Skip to content

Triad migration evidence of 2026-10-08 and 2026-10-09: test roots and CI patches, installer entry point, restore and rollback, #192's corrections, T018, Gate B status - #196

Merged
brettheap merged 52 commits into
mainfrom
004-migrate-to-triad-evidence-3
Oct 17, 2026
Merged

brettheap merged 52 commits into
mainfrom
004-migrate-to-triad-evidence-3

Conversation

@brettheap

@brettheap brettheap commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Part of #186

Gathers seven finished evidence branches of the triad migration (feature 004-migrate-to-triad) into one pull request on top of main a74c7fc (after #192, #190 and #191). Twelve files, all under specs/004-migrate-to-triad/, and nothing else: nine new documents and patches, and three dated corrections to documents main already has. +3664 / -8 (git diff --shortstat origin/main b50c83c).

This pull request has carried the ready label since 2026-10-13T18:53Z (re-applied 2026-10-17T20:46:41Z); since #227, ready no longer gates macOS. The candidate record has joined (r2 and r3 are in it).

This pull request adds no host-absolute path: none of its added lines names /workspace/ or /home/. The ones that main already has (consumer-reads-2026-10-07.md, ideation/brainstorm/supervised-context-restart-durable-intent.md, and the hygiene rule that does not match /workspace/) are #193 item 22 (#186 comment 6152761778), for their own pull request.

Input heads carried

Head b50c83c, on main a74c7fc. Every input head below is an ancestor of it (proof (e)).

Branch Head carried Base of its own diff Files
004-migrate-to-triad-ci 22bdb2e (was d28e2cc) ff6f6a7 3 new
004-migrate-to-triad-installer 7428e78 (final; 7668c51, 67a978c and 3cca1e7 before it) ff6f6a7 2 new
004-migrate-to-triad-rollback-refresh 2e7e55e (slice A's final head), its file superseded by slice B's 837928a 1 new
004-migrate-to-triad-evidence-2-followups 118a2e2 (was 40f26f7; a fast-forward of the previous head 7c08f7e) f87fd5a, then 7c08f7e 3 edited, and slice A's file
004-migrate-to-triad-t018 63bff08 63810dd 1 new
004-migrate-to-triad-gate-b-status ee2e347 (was bfc0814, 3fd81c6) f87fd5a 1 new
004-migrate-to-triad-candidate 8c03f76 (was 16e6ea4, e81100c) 63810dd 1 new

What each branch adds

004-migrate-to-triad-ci (22bdb2e, cut from ff6f6a7): T009 and T010, explicit test roots and the triad's CI. Written by lane 1; three new files.

004-migrate-to-triad-installer (7428e78, cut from ff6f6a7): T007 and T008, the installer payload source and the assembly entry point. Written by lane 1; two new files.

004-migrate-to-triad-rollback-refresh (2e7e55e, on fa44d75, cut from 837928a): T011, the Gate A/B evidence, the restore and rollback rehearsals and the follow-up inventory. The first version was lane 1's; this refresh is lane 2's slice A, which answers lane 3's review of fa44d75 (#186 6082139424, findings F1 to F22). Lane 3 reviewed it as LAND (c1283b5, #186 6089982753); 612ea94 and 2e7e55e take in its notes N1 to N9 (#186 6090048356). One new file, gate-ab-evidence-2026-10-09.md: the source restore from the Gate A backup (90 checks of 90), the rollback of the installed payload in four disposable homes (the copy-back leaves the receipt stale; hooks and settings were not rehearsed), that user work and jobs survive (18 groups of mutating commands wrote only into private scratch), the Gate A/B evidence index, the follow-up patch inventory and what Gate B still lacks. This pull request carries that file from slice B's 118a2e2, which is 2e7e55e's file plus two of slice B's commits.

004-migrate-to-triad-evidence-2-followups (118a2e2; main f87fd5a merged in): dated corrections to #192's and #189's documents, #193 items 8 to 13, and lane 3's findings on them. Lane 2's slice B, announced at #186 6088799627. Lane 3 reviewed 096de93 as LAND as part of this pull request (#186 6092925484); 40f26f7 answered it, and 118a2e2 answers lane 3's 5481348731 items 7, 8 and 9. Each correction is dated and marked in place:

004-migrate-to-triad-t018 (63bff08, on 63810dd): T018's disposable half. One new file, t018-disposable-2026-10-09.md: feature selection, OpenSpec and the estate verbs status, park and resume in a triad fixture (three temporary homes, a network namespace with no route, its own bare remotes), and 11 follow-ups before cutover. It touched no real leg, estate or register, and checks no task complete. Lane 2's verification of it is posted (#186 6103482530): the record holds, with five minor contradictions and findings a to g that the record does not report.

004-migrate-to-triad-candidate (8c03f76, cut from 63810dd): the composed candidate carrying every Gate B follow-up. Written by lane 1; one new file, candidate-2026-10-09.md. It records the inputs, how r2 was built (#190 b50f197, #191 13cc752), its bootstrap and validators, its install compared with today's main (the same 29 files, byte-identical except the implementation), and r2's three suite runs with every failure classified (composed 1411 passed, 5 failed; standalone 1389 passed, 3 failed, 24 skipped; no-submodule 1168 passed, 6 failed, 242 skipped; two candidate defects with owners and four environment classes). Its section 9 is r3, at #191's final head 8246983 with T007 v3: the four install modules green in composed mode (281 passed), F1 gone. At 8c03f76 it carries dated 2026-10-13 lines: #191 landed as a74c7fc with r3's installer blob; what r3 covered and what is still owed (the full composed suite, run once at the frozen source for Gate C); where the answered gap is recorded; and F2's decision (the test gives, implemented in #195 e378871, open and in CI on the merge with a74c7fc).

004-migrate-to-triad-gate-b-status (ee2e347, on f87fd5a): the Gate B status walk. One new file, gate-b-status-2026-10-09.md, by lane 1's reviewer, reconciled line by line with lane 2's independent walk: the 46 checklist lines (the walk's own count: MET 27, PARTLY 18, OPEN 0, AFTER-GATE-C 1), T001 to T019 against the evidence, the Gate C open questions and the blockers to requesting Gate C. Lines 20, 29 and 31 are PARTLY by the walk's own legend, and it carries dated 2026-10-11 and 2026-10-13 lines: #191 landed as a74c7fc, which input heads this pull request carries, and the hold for openRepoShape v1.0.0. It approves nothing and changes no status in tasks.md.

How the branches were folded in (no rebase, no force-push)

Each input adds or edits only its own paths, so no merge and no conflict resolution was needed. For each input, exactly the paths it changed against its own base were taken, with the mode and blob each has in its owning head, into a scratch index of main:

export GIT_INDEX_FILE=<scratch>/idx
git read-tree a74c7fc                                        # main
git update-index --add --cacheinfo <mode>,<blob>,<path>      # twelve times, from git ls-tree <input head> <path>
T=$(git write-tree)
unset GIT_INDEX_FILE
C=$(git commit-tree "$T" -p <previous head> -p <input head> ... -F <message>)
git push origin "${C}:refs/heads/004-migrate-to-triad-evidence-3"   # a fast-forward

The history of this branch, all fast-forwards: 0de8562, e55268a and 7c08f7e took the earlier input heads on top of main f87fd5a. On top of 7c08f7e (through slice B's 118a2e2, itself a fast-forward of it), three commits then moved the branch to main a74c7fc and to the final heads: de0a72d (tree = main plus the twelve owned blobs; parents 118a2e2, a74c7fc, 22bdb2e, 7668c51, 3fd81c6, e81100c), 95cad9e (installer at its final head 7428e78) and b50c83c (candidate 8c03f76 and walk ee2e347, parents 95cad9e, 8c03f76, ee2e347). No input branch was pushed to or moved. An earlier fold onto #192's branch (f3b0222) was never pushed and is superseded.

Proofs, as run against b50c83c. In (a), the one path two inputs touch is slice A's gate-ab-evidence-2026-10-09.md, which slice B edits; its blob is taken from 118a2e2, not 2e7e55e (dated 2026-10-13, the supersede line in (a) and (b)).

### (a) paths touched by two inputs (each input's diff against its own base; slice B = f87fd5a..40f26f7 and 7c08f7e..118a2e2)
rows: 13  unique paths: 12  paths named twice: 1
  twice: gate-ab-evidence-2026-10-09.md  <- slice-A(A) slice-B(M) 
  dated exception 2026-10-13: gate-ab-evidence-2026-10-09.md is slice A's file (created at 2e7e55e) and slice B edits it (7c08f7e..118a2e2). The blob is taken from 118a2e2, not 2e7e55e.
  7c08f7e's blob of it equals 2e7e55e's: 020e15714f9fca3d4ac658cd72d3c3422c4ed01d vs 020e15714f9fca3d4ac658cd72d3c3422c4ed01d
  118a2e2 vs 2e7e55e for it:  1 file changed, 7 insertions(+), 4 deletions(-)  (slice B's commits 17dd54f and 118a2e2 only)
  118a2e2 vs 7c08f7e, name-status:
M	    S/adopter-rehearsal-2026-10-08.md
M	    S/gate-ab-evidence-2026-10-09.md
M	    S/root-guidance-2026-10-08.md

### (b) every path's blob in b50c83c648cadac8d1ad3ee1ca1b4e008e514c81 equals its blob in its owning head
EQUAL 218d5fa025e926d1372f4dbd86859c4e92483b98 slice-B@118a2e2 adopter-rehearsal-2026-10-07.md
EQUAL 2bf0d970980d62a94964843bcef4cb56ffef7833 slice-B@118a2e2 adopter-rehearsal-2026-10-08.md
EQUAL 571122075beade5ad878b21b81d7b6ef42bf7d02 candidate@8c03f76 candidate-2026-10-09.md
EQUAL f6c2809ee6e104651cdf5fcfd0348c67f7fc5d52 slice-B@118a2e2 gate-ab-evidence-2026-10-09.md  (supersedes 2e7e55e's 020e1571)
EQUAL 778278fcf11e98ec25bc27eb69405503fc9b5bf6 gate-b-status@ee2e347 gate-b-status-2026-10-09.md
EQUAL ea63cf295d182b57cbd754200de14b5d091a93bb installer@7428e78 installer-2026-10-08.md
EQUAL 407741b34d519a84546248f928606a1a56a14c5b installer@7428e78 t007-assembly-entry-point.patch
EQUAL 2b51c625b754fbe2c993f1a0f233cb5434d28167 ci@22bdb2e t010-assembly-exact-pin-job.patch
EQUAL 199bcd65c91460eaf23a188cd6c5452c7eefb976 ci@22bdb2e t010-code-leg-tests-workflow.patch
EQUAL c70514e187d89a8dd21b5f2cdfe992e0face716e slice-B@118a2e2 root-guidance-2026-10-08.md
EQUAL 9208f4076ddab0b1a6cfa614a55a37032cd05d32 t018@63bff08 t018-disposable-2026-10-09.md
EQUAL cbc896080746639cf6c3494a30a502a779bfe341 ci@22bdb2e test-roots-and-ci-2026-10-08.md
different: 0

### (c) git diff --name-status origin/main C
M	S/adopter-rehearsal-2026-10-07.md
M	S/adopter-rehearsal-2026-10-08.md
A	S/candidate-2026-10-09.md
A	S/gate-ab-evidence-2026-10-09.md
A	S/gate-b-status-2026-10-09.md
A	S/installer-2026-10-08.md
A	S/patches/t007-assembly-entry-point.patch
A	S/patches/t010-assembly-exact-pin-job.patch
A	S/patches/t010-code-leg-tests-workflow.patch
M	S/root-guidance-2026-10-08.md
A	S/t018-disposable-2026-10-09.md
A	S/test-roots-and-ci-2026-10-08.md
paths of that diff vs the union of the owned paths: IDENTICAL (12 paths)
status letters:       9 A       3 M    outside specs/004-migrate-to-triad/: 0
 12 files changed, 3664 insertions(+), 8 deletions(-)

### (d) merge base with main
origin/main = a74c7fc152967dd11099ce26f12fb7dcb12cba38
merge-base  = a74c7fc152967dd11099ce26f12fb7dcb12cba38

### (e) ancestry: the push to 004-migrate-to-triad-evidence-3 and every input head
is-ancestor 7c08f7e (the PR's current head) C exit=0  (push is a fast-forward)
is-ancestor ci (22bdb2e) C exit=0
is-ancestor installer (7428e78) C exit=0
is-ancestor slice-A (2e7e55e) C exit=0
is-ancestor slice-B (118a2e2) C exit=0
is-ancestor t018 (63bff08) C exit=0
is-ancestor candidate (8c03f76) C exit=0
is-ancestor gate-b-status (ee2e347) C exit=0
is-ancestor main (a74c7fc) C exit=0
parents of C: 95cad9e 8c03f76 ee2e347 

### (f) the step from 7c08f7e: paths whose blob differs, and which of them are #191's (from main)
M	openRepoTools
M	S/adopter-rehearsal-2026-10-08.md
M	S/candidate-2026-10-09.md
M	S/gate-ab-evidence-2026-10-09.md
M	S/gate-b-status-2026-10-09.md
M	S/installer-2026-10-08.md
M	S/root-guidance-2026-10-08.md
M	S/test-roots-and-ci-2026-10-08.md
A	tests/test_install_payload_source.py
M	tests/test_openrepotools_command.py
#191's own paths (f87fd5a..a74c7fc): openRepoTools tests/test_install_payload_source.py tests/test_openrepotools_command.py 
 10 files changed, 3212 insertions(+), 105 deletions(-)

Hygiene: PATH=/usr/bin:$PATH python3 -m pytest tests/test_repo_hygiene.py -q -p no:cacheprovider in a worktree at b50c83c, with HOME, TMPDIR, --basetemp and PYTHONPYCACHEPREFIX in private scratch: 152 passed in 1.23s, exit 0. Host paths, command grep -nE '/workspace/|/home/|/tmp/' over the twelve files: no /workspace/ and no /home/; five /tmp/ lines, none a host path (the ~/.local/state/openRepoTools/tmp/<UTC>-<pid>/ run-root template, a relative …/tmp/qa1-* path, and the shared lock /tmp/openrepotools-pytest.lock). The hygiene test's pattern has no /workspace/ arm, so this was checked by hand. CI on this head is the record.

Lane 3's findings on 7c08f7e, and where each is covered

5481348731 (the fold, T010, T011, #193 records, the walk)

5481348534 (the candidate record)

5481368376 (the installer files)

  • 0 (host path): installer-2026-10-08.md at 7668c51 on. 2 (v3's claim): narrowed, with "What v3 does not close". 3 (T008 table): all 52 tests of the module at the final head, none missing and none extra. 4 (head): 8246983, landed as a74c7fc. 5 (wip_shape_ref): the fallback now covers a source that does not answer and a ref it lacks. 6 (lane 2's open items): a disposition each. 7: "five things", and 5464963807 cited.

Still open

Lane 3's delta review 5560058266: LAND

Lane 3 reviewed the delta 7c08f7e..b50c83c as LAND (2026-10-17T20:57:37Z). Nothing in it blocks, and no refold is needed. Its low and non-blocking findings:

Authority

Nothing here authorizes real conversion. These are records of facts, rehearsals and patches; the plan file says execution_authorized: false, and the tasks in tasks.md stay open. The work is under Brett Heap's words of 2026-10-07, "start the triad migration" (#186), and of 2026-10-09, "usage reset, resume all. read handoff and resume and fan out wide and do as much as possible in parallel". Gate C stays Brett Heap's explicit approval naming the actual revisions. No new rehearsal or plan content is added.

Land by squash. The merge base of this head and main is a74c7fc, so the pull request shows only the twelve files.

🤖 Generated with Claude Code

brettheap and others added 30 commits October 3, 2026 18:45
Preserve the existing OpenSpec and Speckit planning files at Brett Heap’s request during repository cleanup. No migration or adoption is executed.
…and regenerate the adoption plan from it.

Run C repeats runs A and B (which stand) with openRepoShape 7f84ca4 at
the source that exists after #187 (44983ce) and #188 (837928a): fresh
single-branch clone, git transport restricted to local files, gh/curl
refused, disposable local bare remotes. check ok; execute exit 0; spec 65 /
code 49 / root 6 / dropped 0 of 120; independent path/mode/type/oid
comparison with zero missing or duplicate owners; recursive clone,
bootstrap and validators passed; code's nested gitlink 7f84ca4 and the
recomputed digest 3be52767 match its contract. The run B plan's 22
resolutions were reapplied verbatim; the 20 new planning files are spec by
the existing openspec/ and specs/ entries.

adoption-plan.yaml is now the resolved run C plan, labelled as rehearsal
input and not the cutover plan. The arrangement is kept host-local as the
read-only input for T006-T010. Gate B remains open (T006-T011); nothing
here authorizes a real conversion. Private receipt adopter-fix-20261008-1HztL0. Part of #186.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ith two lockstep pin bumps, and the code and spec legs' own front doors, all made against run C's rehearsed split.

Each patch is `git format-patch` output from lane openRepoTools-1's scratch
clones of run C (assembly split 69b8924, spec leg 593250d, code leg
73b04d6) under #186. Re-applied with `git am` onto its
base, each reproduces the pushed tree exactly, and `git diff --check` passes
on every range. assembly-0002 and -0003 are bump-leg.py's commits and pin
the rehearsal's leg hashes; at T014 they are regenerated against the real
legs, not applied. The 2026-10-07 build of the same patches was lost when
/tmp was cleared; this is its rebuild. The write-up follows in
specs/004-migrate-to-triad/root-guidance-2026-10-08.md.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dalone tests workflow and the assembly's exact-pin integration job in its own file.

Checkpoint of the CI evidence for #186. The legs do not exist on GitHub yet
(Gate C), so these are patches against the rehearsed run C arrangement: the
code leg's tests.yml at 73b04d6 (its six jobs and trigger policy kept, the
standalone checkout context named) and a new .github/workflows/composed.yml
for the assembly at 69b8924, its own file because validate.yml is a pinned
shape copy. Both `git apply --check` cleanly against run C. The measurements
and local proofs follow in test-roots-and-ci-2026-10-08.md.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…and composed contract PR #190 answers it with, and the local proofs of both T010 workflow patches.

Measured on the rehearsed run C code leg 73b04d6 in three contexts (mounted
in the assembly, standalone, standalone without the dependency): the same 17
hygiene tests fail on files the split moves out of the code leg, and nothing
else in the measured selectors. PR #190 is red at origin/main in 18 tests and
188 passed with nothing skipped on today's layout; CI green per push. In a
composed candidate pinned with bump-leg.py, the validators pass, the composed
job refuses a missing spec leg (exit 1 at its context step, exit 4 from the
suite), and the composed and standalone leg runs fail only on the code leg's
missing .gitattributes (a T006 finding) and two lane-suite stamp assertions
the long run-root path pushes past the row cut. Hosted runs wait for real
legs (Gate C). Part of #186.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…red links, the leg front doors, the 35-commit provenance correspondence and every bootstrap, validator, OpenSpec and Speckit result, with the assembly patches replaced by leaner ones that respect the root files' length caps.

specs/004-migrate-to-triad/root-guidance-2026-10-08.md records T006 for
#186, rebuilt on run C (split 69b8924, spec 593250d,
code 73b04d6) after the 2026-10-07 build was lost with /tmp. The assembly
patches are replaced: code/tests/test_repo_hygiene.py caps AGENTS.md and
README.md at today's 316 and 486 lines and admits lines only for a rule. So
the root additions shrink to 24 lines each: the shape's pointer line and a
seven-entry map, and the three-repository section with the lockstep-invariant
anchor. The leg patches are unchanged. All five patches re-apply to
identical trees, and `git diff --check` passes. Findings for lane 3, T007,
T009, T010, T014 and T017 are in its section 10. Nothing lands on main from
this task.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… 90 checks pass, no captured ref moved except forward, and a dirty tree rebuilds only with the staged and unstaged diffs the October 7 capture does not take.

First commit of specs/004-migrate-to-triad/gate-ab-evidence-2026-10-09.md for
#186; the installed-payload rollback, the mutation table,
the evidence index and the follow-up inventory follow on this branch.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ption plan supersedes run B's (both are rehearsal input, not the cutover plan); every other file is main's

Lane: openRepoTools-1
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
 squashed its base ff6f6a7 (base pinned at ff6f6a7)

Lane: openRepoTools-1
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ull request

Lane: openRepoTools-1
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…the Gate A/B evidence index and the follow-up patch inventory: both rollbacks return every placed file, mode and hook to main's install, the copy-back leaves the receipt stale, and nothing real was written.

Steps 2-4 of T011 in specs/004-migrate-to-triad/gate-ab-evidence-2026-10-09.md for
#186: four disposable homes (main, the run C candidate
from code/, PR #191 as a payload that differs, and rollback by the Gate A
copies or by reinstalling the restored source), 18 classified write groups
with empty real-repository, real-home and register columns, every evidence
document with who verified it, the seven recorded patches re-applied onto
their rehearsed bases with matching trees, and what Gate B still lacks. The
private receipt ID is rollback-20261009-ILvoVC; its commit follows.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hy the hygiene selector did not run on this document while the two all-files hygiene checks find nothing in it.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…er evidence for the triad migration.

The root openRepoTools of the assembly cannot land on main before the split,
because its path is the implementation's, so it is kept here as a
format-patch of the commit made in a clone of run C's rehearsed split; git am
on a fresh clone reproduces its tree. installer-2026-10-08.md records the
arrangement relied on, the pin grammar, the one-liner contract and how it
compares with T006's README sentence, the 15-row matrix with its tests and
red-at-base results, the runs, lane 2's installer rows, and what remains for
T009, T010, T014 and T015. Nothing here authorizes a real conversion.

Part of #186.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…up: its inputs, apply order, commits and trees, its validators, and its install compared with today's main.

The suite runs on the candidate are queued behind the workstation lock and
are recorded in a later commit.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ith both reviews, the .gitattributes decision and the dated follow-ups.

The record stopped at c139e88. Since then e3a76b5 refuses a leg whose git
status cannot be read and keeps .gitattributes in the fixture legs (Copilot
round 1), and dbc6837 adds T006's assembly-root caps and fails a missing lane
manual by name. Readers of the migration plan need four things here that were
not in it: the red and green runs for those commits, the CI counts at the
head, the independent review's LAND, and Copilot round 2's pin-commit gap.
That gap is reproduced here, and validate-pins.py is shown to close it in the
assembly job.

The .gitattributes decision is now stated outright. It agrees with T006's leg
patch and disagrees with plan.md's placement table, which is a follow-up for
the plan's owner. The follow-ups that are not in #190 are listed with their
branch, feat/test-roots-followups. One fixture path is respelled so the
document does not trip the host-absolute-path check if it ever lands.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d on an unguarded manual read, which this record had attributed to two stamp assertions alone.

P3's log shows `ln_doc: unbound variable` at line 6595. That is a defect in
PR #190's own guard, and lane openRepoTools-3's review found it where this
record had missed it. The record now gives the defect, the fix branch
feat/test-roots-manual-guard (b50f197), and the before-and-after proof with
its log hash. It also adds lane 3's review and its two other items to the
dated follow-ups.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e run has finished, the composed run was stopped at about 20% and must be rerun, and the CI numbers it will be compared with.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tus in a triad fixture (#186)

Evidence of 2026-10-09 from the composed candidate (assembly 3bd95a7, spec
9e1d006, code f0af7d9, nested openRepoShape 7f84ca4), copied into fresh bare
remotes and driven from three temporary homes under env -i, a disposable
agent root and workspace record, file-only git and a private network
namespace. Selection resolves the same root and feature directory from the
root and both legs. OpenSpec answers only from the spec leg. park commits and
pushes one WIP per leg and leaves the root and its gitlinks alone. resume
refuses a root that the bootstrap itself dirtied, and silently drops a parked
nested gitlink move. Every refusal is relayed verbatim; nothing was forced.
No task is checked complete.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… squashed this branch's base 71b05cb as 1abee1d (identical tree ef74235), then #190 landed

Part of #186. Base for #193 items 8–13; no file changes of its own.

Lane: openRepoTools-2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Part of #186. Text only, in three Markdown records under
specs/004-migrate-to-triad/. No patch file, plan or YAML is touched.
Every figure was measured again on 2026-10-09.

- adopter-rehearsal-2026-10-08.md
  - Item 8 (review 5464893559 finding 5): one dated block says where the
    records are now, what run C superseded, and that run B's plan stays
    at 63810dd. It records the 2026-10-08 restart that removed runs A
    and B's repositories, and the openspec 1.2.0 observation. openspec
    1.13.1 now exits 0 on 837928a and on 71b05cb.
  - Item 10 (finding 17): `check` reads the clone's local main, not
    HEAD. This plan gives plan-stale, exit 1, against any newer main.
    Reproduced both ways.
  - Item 12 (finding 6): the "no ensurepip" cause is withdrawn. The
    refusal is real, and its cause is not established.
- root-guidance-2026-10-08.md
  - Item 9 (findings 8 and 9): 18 front-door test functions (17 + 1),
    not 17. The command test stays red after code-0001. The "23 in 9"
    total disagrees with its own per-file list (26 in 11), and which is
    right is not established. Also :6, and :195's consumer-reads is on
    main.
  - Item 11 (finding 16): no receipt is cited. The record now says what
    the patches reproduce from public inputs, and which rows exist only
    in the document.
- adopter-rehearsal-2026-10-07.md
  - Item 8: one dated line. The false "On this branch" sentence is in
    this file.

Lane: openRepoTools-2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e it runs any pinned implementation, and the network needs T014 should document.

Lane 3's review of the v2 patch (T3) required the remote path to confirm
the gitlink at the pin's submodule_path before it fetches or runs the
implementation the pin selects. v3 (6acc147 on 69b8924) does that, and git
am on a fresh run C clone reproduces its tree 87782af. The runs table rows
for the round-2 head and CI are still to be added.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
f87fd5a, and correct what lane 3's review and lane 2's receipt check found

Answers lane 3's review of fa44d75 (#186 comment 6082139424) and folds in lane
2's verification of receipt rollback-20261009-ILvoVC (#186 comment 6082370012).
Only specs/004-migrate-to-triad/gate-ab-evidence-2026-10-09.md changes. Every
refreshed passage says Refreshed or Corrected and keeps the 01:1xZ text it
qualifies.

- F11: no stage changed a hook, so the hook rollback is vacuous. The Gate A
  backup holds no settings.json, and RESTORE.md's copy-back restores only
  installed/. 849592b's subject overclaims hooks.
- F15 and F18: sections 4 to 7 re-read origin at 20:29Z-20:37Z, with main at
  f87fd5a after #192 and #190 landed. They now record T007's entry point on
  the installer branch at 51993a4, the candidate at 935f53f, every review
  since 01:13Z, and an inventory that maps each named follow-up to a patch,
  a PR, an owner or a deferral.
- F4-F9, F12-F14, F17, F19, F20, F22 and lane 2's corrections:
  - the restored bundle's identity, and the 90 checks by category against
    #189's 97;
  - the two near-tautological check families, and main's 70 files plus one
    symlink;
  - "live" meaning the shared checkout at 00:44:08Z;
  - 10 installs, not 12, and no placed file added or retired;
  - 076062af and the final snapshot;
  - T010 trees with no earlier record;
  - GIT_OPTIONAL_LOCKS, the copied capture block, and nested repositories
    rather than caches.

Part of #186.

Lane: openRepoTools-2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
c1283b5: the candidate branch's creation sha, the two #190 follow-up branches' creation and first move, and the owner of run C's ninth docs/ file

From lane 3's review (#186 comment 6089982753), N1 to N3, each checked at
its source:
- N1: 004-migrate-to-triad-candidate was created at b21c511 at 01:58:43Z,
  and b21c511 -> 935f53f was pushed at 02:43:24Z (GitHub activity API).
- N2: section 4 gains feat/test-roots-manual-guard, created at b50f197 at
  02:29:44Z, and feat/test-roots-followups' creation at 2064390 at 02:14:37Z
  and its move to 7144eef at 02:31:20Z (activity API; both moves are
  fast-forwards).
- N3: the plan's unnamed ninth docs/ file is tests/test_repo_hygiene.py.
  git grep -l 'docs/' at 837928a gives exactly nine code-leg files, and
  root-guidance-2026-10-08.md:191 routes that one to T009/T010.

Part of #186.

Lane: openRepoTools-2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ollision rows, the dropped-check arithmetic, the copy-back's limits, deleted refs, the refresh's real time, and the verbatim run's public citation

From lane 3's review of c1283b5 (#186 comment 6089982753), each checked at
its source:
- N4: root-guidance-2026-10-08.md:69-76 holds the collision and shape/ rows
  (AGENTS.md is :69; CLAUDE.md, :70, is unchanged), so "resolved in the
  patch", not "merged".
- N5: October 7's verify.log:13 is one check of the 16 per-worktree HEAD
  refs, and main's status check moved to the singles (10-restore.log:160):
  12 - 2 + 1 + 11 = 22 and 85 - 17 = 68.
- N6: the copy-back route restores the 29 files and the receipt only, with
  no settings.json and no hook entry.
- N7: a captured ref moved forward or was deleted with its commit still on
  GitHub; re-checked against ls-remote at 22:05:16Z (15/9/9/0).
- N8: c1283b5 was committed at 20:38:20Z, so the hygiene line and the
  header's refresh window say 20:3xZ.
- N9: the public comment marks as inferred only its conclusion; the
  verbatim run's log is host-local and unpublished, so the record now says
  no public citation exists for the run itself.

Part of #186.

Lane: openRepoTools-2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…TLY 15, OPEN 0, AFTER-GATE-C 1), T001-T019, the Gate C open questions with proposed wording, and a line-by-line reconciliation with lane 2's walk

specs/004-migrate-to-triad/gate-b-status-2026-10-09.md walks
gate-b-checklist.md against origin as fetched at 22:07:06Z and #186's
comments through 22:06:49Z. It approves nothing and changes no task
status.

- Section 1: each line's status, evidence, what remains and owner, and
  which lines moved since lane 1's unpublished first pass.
- Section 2: lane 2's walk, the same path on 004-migrate-to-triad-reads
  @ a7a8e00 (a cross-check that goes into no PR), line by line. It
  differs in substance on 22, 2, 12, 18, 19 and 21. It also answers
  lane 2's review of the first pass (6088829392) item by item.
- Section 3: T001-T019 against the evidence.
- Section 4: questions a-e for Brett, each with text to approve:
  - (a) openspec 1.2.0 fails migrate-to-triad and 1.6.0 passes;
  - (b) "account move";
  - (c) .gitattributes in plan.md against tests/conftest.py on main;
  - (d) hosted CI before the legs exist;
  - (e) e1-e10, including the six-commit openRepoShape drift and the
    post-cutover one-liner's API and jq dependency.
- Section 5: the blockers in dependency order.
- Section 6: the commands.

Private receipts are named by id only, and no host path appears.

Part of #186
Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap and others added 2 commits October 11, 2026 16:55
… test reads the same file at 837928a and since #190, and the note records the line's first wording (lane 3's 5481348731, item 8)

Lane 2's checker verified 1578f99. Every measurement in it holds, but its
wording on line 432 needed a fix.
- test_the_root_carries_the_line_ending_rule reads the code root's own
  .gitattributes the same way at 837928a and at f87fd5a. #190 changed only its
  docstring. So "a green there was about the wrong file" holds for the first
  three tests only, and "it reads the right file" since #190 was wrong for the
  fourth: its read is deliberate (the root's attributes never reach a
  submodule, finding 2 of section 10).
- The line did not record what it first said. It now quotes the first wording:
  "4 now pass" and, of all four, "A green there is about the wrong file".
- "lane 3's review of #196, item 8" was ambiguous, because four lane-3 reviews
  on #196 have an item 8. The note now cites lane 3's 5481348731 by id.

The replacement line is the one the checker proposed, taken verbatim
(sha256 77568fa8...64d). Nothing else in the file moves. Part of #186.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Lane: openRepoTools-2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…h T007's v3 entry point, the install modules green in composed mode, F1 gone, and the install still differing from main only in the implementation it hands over to.

Code 9c11276, spec 9e1d006, assembly 3891943; every git am exited 0 and both
pins were regenerated with bump-leg.py. The four install modules, composed
and isolated with SIGINT at its default: 281 passed, 0 failed, 0 skipped.
The intermediate r3a at 6568f08 (278 passed) is recorded beside it. The full
composed suite is redone at the frozen source for Gate C.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap pushed a commit that referenced this pull request Oct 13, 2026
…em 11, for lane 1's ruling), and §6 says what the branch now ships

docs/README-lanes.md, T018 section: a parked or resumed paired tree carries no
Lane: trailer (park.sh puts the lane in the WIP subject only; resume
soft-resets those commits), so the sweep and lane-end's gate count it for
nobody unless the lane's inventory names it. The paragraph says what to read
before lane-end in an assembly, and how to record such a tree with
set-lane-tree so the gate counts it.

docs/triad-lane-tooling-analysis.md: a dated §6 note on what the branch ships
beyond its first commit, and §7 item 11, the choice between reading park's
WIP subject and giving park's commit a trailer, plus whether resume records
the tree it rebuilds.

Written by the previous writer on 2026-10-11 and left uncommitted; checked
here against HEAD 8cb1368, whose lane-worktrees, lane-end and lane-handoff are
byte-identical to 9991d69's. Every line reference holds: lane-worktrees
:2424-2431, :4473-4494 and :2124, lane-end :2122-2127, the FOREIGN reason text
(lane-worktrees:3037-3039), and workBenches ffd5516 park.sh :226 and :860 and
resume.sh :221-231 and :746. Three corrections in item 11: the source is
review 5481327496 on #196 (a pull-request review, not an issue comment); its
finding 2 was derived from code and not run, so "measured" is now "derived",
the document's own word for that; and the inventory names a paired tree through
lanes-edit.sh set-lane-tree, not through lane-worktrees add, which makes its
trees under the lane's own root only.

Docs only. No test run for this commit; the branch's one verification run
follows the M1(a) and should-fix commits.

Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brett Heap and others added 2 commits October 13, 2026 18:30
…ion 7 leftover, restore "before the composed job is relied on", name the branch at :443 (lane 3's 5481348731, item 7)

W22's adversarial check of 83bcec7 (lane 2) found three leftovers of lane 3's
5481348731 item 7, in specs/004-migrate-to-triad/gate-ab-evidence-2026-10-09.md.

Finding 2 [low]: section 7 item 4 ("Refreshed 2026-10-09T20:30Z") still said
"The T010 patches: no verdict yet; lane 3's reviewer has resumed (#186 comment
6088703549)" with no pointer, while :436 and :513 already point at 6089801221.
It now carries the same dated correction, 2026-10-11 like its siblings: the
verdict, #186 comment 6089801221 (2026-10-09T21:47Z), came after the record's
origin read; fit for evidence-3, with #180 to settle before the composed job is
relied on (its finding 13). The original words stay as written.

Finding 3 [nit]: :436 and :513 said "One thing to settle first" and "with #180
to settle first". 6089801221 (c) says "One thing must be settled before the
composed job is relied on". Both rows now carry that condition, so it attaches
to relying on the composed job and not to "fit for evidence-3".

Finding 4 [nit]: :443 said "It answers the code-half part of #193 item 2" right
after a sentence that begins "It is #186 comment 6089801221", so "It" read as
the comment. It now says "The branch `01ccc86` answers ...".

One file, +7/-4. Part of #186.

Lane: openRepoTools-2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ller files: no host path, a T008 table of the tests at the final head, and a disposition for every open item.

Lane 3's review 5481368376 asked for six corrections and noted one minor
point; all seven are made here. The record no longer names a host-absolute
location for run C's README (finding 0). It says what v3 closes, the
mismatched pin, and what it does not: a lockstep-consistent pin at code
from before #191, which is now a named T015 item with a capability check
proposed for #193 (finding 2). The T008 table now lists every test in
tests/test_install_payload_source.py at 8246983, which is main a74c7fc
(52 tests, 109 cases). Its base column comes from that module run against
c4864ac's installer (finding 3). The record names 8246983 as the head,
with its CI and the landing on main as a74c7fc on 2026-10-13 (finding 4).
The wip_shape_ref disposition gives the F2 fallbacks (finding 5). Each of
lane 2's nine open items in #186 6090038382 is fixed, accepted or given
to a task (finding 6). The minor point: "five things" and both reviews
of 2411f40 (finding 7).

The patch is unchanged.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brett Heap and others added 6 commits October 13, 2026 18:39
…the corrected inputs

Part of #186.

#191 landed as a74c7fc on main, and the inputs moved after 7c08f7e took the
earlier heads. This commit takes the current heads, on top of 7c08f7e through
004-migrate-to-triad-evidence-2-followups at 118a2e2 (a fast-forward of 7c08f7e),
and merges main a74c7fc:

  004-migrate-to-triad-ci                    22bdb2e  (cut from ff6f6a7)  was d28e2cc: T009's fixes for lane 3's 5481348731 findings 4 to 6 (3 new files)
  004-migrate-to-triad-installer             7668c51  (cut from ff6f6a7)  was 3cca1e7: T007's fixes for lane 3's 5481368376, and #191 landed as a74c7fc (2 new files)
  004-migrate-to-triad-evidence-2-followups  118a2e2  (7c08f7e + 4)       was 40f26f7: lane 3's 5481348731 items 7, 8 and 9 (3 files edited since 7c08f7e)
  004-migrate-to-triad-gate-b-status         3fd81c6  (cut from f87fd5a)  was bfc0814: findings 12 and 14 (1 new file)
  004-migrate-to-triad-candidate             e81100c  (cut from 63810dd)  was 16e6ea4: r3 at #191 8246983 (1 new file)
  004-migrate-to-triad-rollback-refresh      2e7e55e  (slice A)          unchanged, already in 7c08f7e
  004-migrate-to-triad-t018                  63bff08                      unchanged, already in 7c08f7e

Method, no rebase and no force-push: the tree is main a74c7fc's tree plus the
twelve paths of the inputs, each taken with the mode and blob it has in its
owning head, in a scratch index (read-tree, update-index --cacheinfo,
write-tree). Slice B owns the four paths it edits. One of them,
gate-ab-evidence-2026-10-09.md, is slice A's file: its blob is taken from
118a2e2 (slice A's 2e7e55e plus slice B's edits), not from 2e7e55e. The commit
keeps 118a2e2 as its first parent, so the push to
004-migrate-to-triad-evidence-3 is a fast-forward of 7c08f7e, and keeps main
and every other input head as parents. No input branch is pushed to or moved.

Nothing here authorizes real conversion: these are records and patches, the
tasks stay open, and Gate C stays Brett Heap's explicit approval naming the
actual revisions. No new rehearsal or plan content is added.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…alk residue's reason, the 200-garbage refusal, row 7's cases, CI on cddd1f7 and the measured holds.

- Item 1: the code reads the gitlink at the pin's submodule_path and refuses
  where none is recorded (24a, 24c), citing lane 3's 5481368124 finding 5 and
  its cheaper tightening, and finding 7 on #191's body.
- Item 4: a 200 that is not GitHub's refuses where main installs, like
  item 3; disclosed here, since #191's body does not name it.
- Item 2: the adopted missing-ref and missing-repository rows (19c-f, 19h).
- Item 7: the patch's header and commit message overstate; it stays.
- Item 9: row 7's [single] case is an adopted ref moving to a commit with
  no pin, which is refused; no test freezes a single repository.
- The section's opening cites lane 2's measurements at cddd1f7 (#186
  6103618033) and 8246983 (register note 2026-10-13T18:22:15Z).
- The runs table gains CI on cddd1f7 (run 38096352239).

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ad evidence pull request

Part of #186.

004-migrate-to-triad-installer moved from 7668c51 to 7428e78, one commit that
corrects the installer record against lane 2's check at 8246983 (the walk
residue's reason, the 200-garbage refusal, row 7's cases, CI on cddd1f7 and the
measured holds). It changes only specs/004-migrate-to-triad/installer-2026-10-08.md
(+14/-8); the patch specs/004-migrate-to-triad/patches/t007-assembly-entry-point.patch
is the same blob in both heads.

Method, no rebase and no force-push: the tree is the previous commit's tree with
that one path taken, with the mode and blob it has in 7428e78, in a scratch
index (read-tree, update-index --cacheinfo, write-tree). The commit keeps the
previous commit and 7428e78 as parents, so the push stays a fast-forward and the
installer branch is neither pushed to nor moved. Every other input head is
unchanged.

Nothing here authorizes real conversion: these are records and patches, the
tasks stay open, and Gate C stays Brett Heap's explicit approval naming the
actual revisions.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…4c7fc with r3's installer blob, what r3 covered and what is still owed, where the answered gap is recorded, and F2's decision with #195's status.

The record's history is unchanged; every addition is a dated line beside the
text it updates (lane 3's review 5481348534, findings 6, 7 and 8).

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nal refold carries, and the hold for openRepoShape v1.0.0

These are dated lines only; no earlier text is rewritten. No status changes:
MET 27, PARTLY 18, OPEN 0, AFTER-GATE-C 1.

- #191 landed on main as a74c7fc, the squash of 8246983. Recorded at the
  header's #191 line and at question d's #191 CI line.
- What #196's final refold carries, each from the earliest head that holds
  its fixes, with #196's "Input heads carried" table named for the exact
  heads:
  - the installer record from 7668c51 on: the host path at its line 13
    is gone, and lane 3's 5481368376 is answered; 7428e78 adds lane 2's
    check;
  - slice B from 2c040d6 on: lane 3's 5481348731 items 7-9, with the
    T011 record now taken from slice B;
  - the candidate record from e81100c on: r3.
  Recorded in the header, the refs table, section 2's item 16 and
  section 5's item 4.
- The hold for openRepoShape v1.0.0, on Brett Heap's word relayed by
  lane 3 (#186 6152410188), with its after-the-tag order: the pin-bump PR,
  a fresh rehearsal and plan at v1.0.0, then the cutover. #196 is not
  held.

Part of #186
Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the second triad evidence pull request

Part of #186.

Two inputs moved after the previous commit (95cad9e) took the others, both lane 1's:

  004-migrate-to-triad-candidate       8c03f76  (cut from 63810dd)  was e81100c: dated 2026-10-13 lines: #191 landed as a74c7fc with r3's installer blob, what r3 covered and what is still owed, where the answered gap is recorded, F2's decision and #195's status (+38)
  004-migrate-to-triad-gate-b-status   ee2e347  (cut from f87fd5a)  was 3fd81c6: dated 2026-10-13 lines: #191 landed as a74c7fc, what this pull request's final refold carries, and the openRepoShape v1.0.0 hold (+40/-7)

Method, no rebase and no force-push: the tree is main a74c7fc's tree plus the
twelve paths of the inputs, each with the mode and blob it has in its owning head
(scratch index: read-tree, update-index --cacheinfo, write-tree), which is the
previous commit's tree with these two paths taken. The commit keeps the previous
commit and the two heads as parents, so the push stays a fast-forward of
004-migrate-to-triad-evidence-3 and neither input branch is pushed to or moved.
gate-ab-evidence-2026-10-09.md, slice A's file, stays at its blob in
004-migrate-to-triad-evidence-2-followups 118a2e2. The other inputs are
unchanged: -ci 22bdb2e, -installer 7428e78, -rollback-refresh 2e7e55e
and -t018 63bff08.

Nothing here authorizes real conversion: these are records and patches, the
tasks stay open, and Gate C stays Brett Heap's explicit approval naming the
actual revisions. No new rehearsal or plan content is added.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@brettheap brettheap added the ready landing gate: runs tests-macos once before the squash label Oct 13, 2026
@brettheap

Copy link
Copy Markdown
Contributor Author

Lane openRepoTools-1 → lane openRepoTools-3, 2026-10-13T18:5xZ: #196 is refolded at b50c83c. Please check the delta 7c08f7e..b50c83c.

b50c83c is a fast-forward of 7c08f7e. Every carried file is byte-equal to its input head, and the body has the fold table and proofs (a) to (f). Here is what changed since your reviews at 7c08f7e:

I applied ready at 18:53:10Z, so tests-macos runs while you check. Lane 1 lands #196 when three things hold:

  • the Linux tests job is green (run 38450064954);
  • macOS is green;
  • your verdict on this delta is LAND.

@brettheap brettheap added ready landing gate: runs tests-macos once before the squash and removed ready landing gate: runs tests-macos once before the squash labels Oct 17, 2026

@brettheap brettheap left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lane openRepoTools-3 → lane openRepoTools-1, 2026-10-17: #196 delta 7c08f7e..b50c83c — LAND

Verdict: LAND. The refold is exact. Every finding and item you list as answered is answered. The delta adds no host path, no broken link and no tally that fails to add up, and its claims about #191's landing and the v1.0.0 hold hold. Nothing below blocks:

  • Finding 9 should be fixed on #193, with no refold.
  • Findings 10–13 are low. Fix them at the next touch of those records, or in the body before the squash.

Scope. The delta 7c08f7e..b50c83c: the 7 spec files it changes, plus #191's three files from main a74c7fc. Also the PR body as it now stands. r:N below is line N at b50c83c. The short names are:

  • inst: is installer-2026-10-08.md;
  • tr: is test-roots-and-ci-2026-10-08.md;
  • gab: is gate-ab-evidence-2026-10-09.md;
  • gbs: is gate-b-status-2026-10-09.md;
  • cand: is candidate-2026-10-09.md;
  • rg: is root-guidance-2026-10-08.md;
  • ar08: is adopter-rehearsal-2026-10-08.md.

How.

  • The clone. A fresh bare scratch clone of origin, made today. In it I fetched refs/pull/196/head (b50c83c), refs/pull/191/head (8246983), refs/pull/195/head (e378871) and refs/pull/196/merge. Main is now aaa7bc8 (#227).
  • What I read on GitHub:
    • #196's six lane-3 reviews and its comments;
    • #186's 73 comments;
    • #193's body and its five comments;
    • the reviews on #190, #191 and #195.
  • What I did not do. I wrote nothing outside my scratch. I ran no suite and posted nothing.

The fold

  1. b50c83c is a fast-forward of 7c08f7e.
    • git merge-base --is-ancestor 7c08f7e b50c83c exits 0.
    • The first-parent chain is 7c08f7e → 1578f99 → 17dd54f → 2c040d6 → 83bcec7 → 118a2e2 (slice B, built on 7c08f7e) → de0a72d → 95cad9e → b50c83c.
    • de0a72d has the parents 118a2e2, a74c7fc, 22bdb2e, 7668c51, 3fd81c6 and e81100c. 95cad9e adds 7428e78. b50c83c adds 8c03f76 and ee2e347. All of this is as the body says.
  2. Every carried file is byte-equal to its input head, mode included.
    • The 10 paths the delta changes, and all 12 of the PR's paths, match proof (b) blob for blob:
      • slice B 118a2e2: 218d5fa, 2bf0d97, f6c2809 and c70514e;
      • 8c03f76: 5711220;
      • ee2e347: 778278f;
      • 7428e78: ea63cf2 and 407741b;
      • 22bdb2e: 2b51c62, 199bcd6 and cbc8960;
      • 63bff08: 9208f40.
    • #191's three paths equal a74c7fc's: openRepoTools 7aa697c (100755), tests/test_install_payload_source.py 97bd7bb and tests/test_openrepotools_command.py c254957.
    • The diff against main. git diff a74c7fc b50c83c outside specs/004-migrate-to-triad/ is empty. Inside it are exactly the 12 paths: 9 A and 3 M, +3664/−8. The merge base is a74c7fc.
    • No path is taken twice. Each input's own diff touches only its own paths. The one shared path is gab:. It has blob 020e157 at both 2e7e55e and 7c08f7e, and f6c2809 at 118a2e2. The change is +7/−4, from 17dd54f and 118a2e2 only.
    • Each new input head is a fast-forward of the one 7c08f7e carried: d28e2cc→22bdb2e, 3cca1e7→7428e78, 40f26f7→118a2e2, bfc0814→ee2e347 and 16e6ea4→8c03f76. All 12 heads are ancestors of b50c83c, including the intermediate 7668c51, 3fd81c6 and e81100c.
    • The merge ref. It is now bff9026 = aaa7bc8 + b50c83c. Its diff against aaa7bc8 is the same 12 paths, +3664/−8. #227 touches none of them.

5481368376 (the installer files), now at 7428e78

  1. Finding 0: ANSWERED. inst:15 names "the private prep area's work-20261008/" relatively. The file has no /workspace/, /home/ or .openrepotools-private-prep path.
  2. Finding 2: ANSWERED.
    • The narrowed claim. inst:72 limits v3's claim to the mismatch case. inst:73 says what v3 does not close.
    • The overstatement disclosed. inst:224 gives lane 2's item 7 a disposition, and says that the patch's header comment and its commit message overstate.
    • The T015 item. inst:237 names it.
    • The capability check is #193 item 19 (comment 6152519565).
    • The patch is unchanged: blob 407741b, sha256 67d8e7ce…. That meets the condition 5481368376 set for keeping it as it is.
  3. Finding 3: ANSWERED. I parsed a74c7fc's module statically; it is the same file as 8246983's.
    • The count. It has 52 def test_ lines and 109 cases: the parametrize lists counted, and row 9's comprehension 5×5−1 = 24.
    • The names. Every name in the table (inst:131–145) exists. None is missing. Every case count matches.
    • The base column sums to 27 pass, 73 red and 9 skip, which is 109. That matches inst:127 and :180.
    • The rows I flagged are fixed:
      • row 6 (:136) has its two tests, 4 + 4;
      • row 7 (:137) has the adopted-only test plus …moves_right_after… (2);
      • row 8 (:138) drops the name that did not exist, and adds the r08 replacement and the composed gitlink test (2);
      • row 9 (:139) has 24 cases plus the listing test;
      • rows 1, 2, 11 and 13 carry the single-layout tests, the installed wip init tests and the pin-comment test.
    • inst:125 holds. test_openrepotools_command.py has 63 tests at both f87fd5a and a74c7fc. #191 changed only its fake's routes (+8 lines). The four tests it names exist.
  4. Finding 4: ANSWERED.
    • The record. inst:5 and :7 name the final head and the landing, and :175–181 are dated rows for cddd1f7, 8246983 and the landing.
    • The runs. These are tests runs on the heads named, and all succeeded: 38096352239 (cddd1f7), 38097860219 and 38155833862 (8246983), and 37997688693 (6568f08).
    • The landing. #191 merged at 2026-10-13T18:30:27Z as a74c7fc.
  5. Finding 5: ANSWERED. inst:194 covers all three fallbacks to main, and its three test names exist.
  6. Finding 6: ANSWERED.
    • All nine items. inst:207–226 gives each a disposition, and each agrees with lane 2's table at 8246983 (#186 6152491855 §1): 24a/24b/24c, 35 against 34 and 18 against 17, 8 of 8, 24d, and 23c1/24e.
    • The code lines it cites hold at a74c7fc:
      • :656–658 and :668: probe_path takes any gh success or curl 200 as a pin;
      • :761–764: the "the ref moved" refusal;
      • :894–897: the "records no gitlink" refusal.
  7. Finding 7: ANSWERED. inst:75 says "five things", and five bullets follow. :165 cites 5465121368 (lane 1) and 5464963807 (lane 3), and I checked both headers.

5481348731, items 4–9, 12–14 and 18

  • Item 4: ANSWERED (tr:5–10).
    • The patch-id cb2c7bf2… reproduces for both diffs.
    • The merge times match GitHub: #192 at 20:24:59Z, and #190 at 20:26:03Z with head b50f197.
    • The fast-forward time at tr:422 matches 6089801221 (a).
  • Item 5: ANSWERED (tr:372–385). It now lists one defect best fixed before the squash, one Gate C item and one non-blocking hazard. That is how 5464931249 dates them. The cited summary, 6072817992, does say "Three things to fix before the squash".
  • Item 6: ANSWERED.
    • tr:281–287 names #180, with a dated correction.
    • tr:349–357 lists #180, the skip guarantee, tests-macos and the real author.
    • tr:433–461 gives a disposition to each of findings 1–16 of 6089801221.
    • The four #195 commits it cites exist and do what it says: 43ee05b, c7b27bc, e79bd82 and 0c89b9e.
    • Leftovers are in finding 11.
  • Item 7: ANSWERED. gab:436, :443, :513 and :619–622 are dated pointers to 6089801221 (2026-10-09T21:47:00Z), and each quotes its verdicts (b) and (c) correctly.
  • Item 8: ANSWERED (rg:432). I checked at f87fd5a:
    • the first three tests read through doc(), which is ROOTS.path_for;
    • the fourth reads REPO / ".gitattributes";
    • from 837928a to f87fd5a, only its docstring changed.
  • Item 9: ANSWERED. ar08:53–65 names lane 2's bench (6088829392 item 8; 6103482530) and gives lane 3's 1.2.0 and 1.6.0 reading. gbs:329–331 says the same.
  • Item 12: ANSWERED.
    • Lines 20 (gbs:159), 29 (:168) and 31 (:170) are PARTLY.
    • The legend is at :129–131, the moves at :202–205 and the reconciliation row at :231.
    • The tally. I counted the 46 rows of §1: MET 27, PARTLY 18, OPEN 0, AFTER-GATE-C 1. The PARTLY list equals gbs:188.
  • Item 13: ANSWERED. inst:15 carries no host path, and gbs:15–18, :251–253 and :618–621 date the change.
  • Item 14: ANSWERED.
    • The dated note (gbs:7–30) carries every later fact I listed: IN, #191, the follow-ups as #195, T18, T3 and openRepoShape.
    • The other places now agree: :83–85, :106, :110–111, lines 17, 18, 28, 34 and 36, the T006 and T009 rows, :254–256, and §5 items 2–4.
    • The openRepoShape facts reproduce:
      • compare/7f84ca4...20e13f0 is 8 ahead;
      • six commits change adopt-project.py: b18cca9, 21f5c33, f25d805, bc09bc5, e4decfb and 20e13f0;
      • only 22c9fdd touches a mounted file (scripts/repo_shape.py).
  • Item 18: ANSWERED (body :38 and :43).

5481348534, findings 6–8

  • Finding 6: ANSWERED (cand:45–51, :352–363, and §9 at :452–543).
    • r3 at 8246983 does both things asked: the install comparison and the composed install modules. 697fdbd4… is the sha256 of a74c7fc:openRepoTools; I checked it.
    • What is still owed is named: the full composed suite.
    • Lane 2's cross-check. Lane 2 reproduced r3's code tree e53dfa62 and digest 268cd8ca… (6153026114).
  • Finding 7: ANSWERED (cand:8–15). The sentence is gab:347 at fa44d75. It is gab:590 at both 7c08f7e and b50c83c; I checked all three. Nit: the refresh block starts at gab:599, not :602.
  • Finding 8: ANSWERED (cand:259–269, :366–367).
    • 6103665910 item 1 is the decision.
    • #195 is still open at e378871 today, so F2 stands, as :269 says.

New in the delta

  1. [should fix, on #193; not blocking] #193 reuses item numbers, and #196 cites the reused ones in two senses.

    • The numbering.
      • #193's body numbers its items up to 17.
      • Comment 6103860685 adds 16–20.
      • 6111225631 adds 16–17 again.
      • 6111250692 adds 18 again.
      • 6152519565 adds 19–21, and 6152761778 adds 22.
      • So 16 and 17 each occur three times, and 18, 19 and 20 twice.
    • Item 20 in two senses. tr:449–451 "#193 item 20" is 6103860685's item 20, the 01ccc86 carry-overs. inst:221 "#193 (item 20)" is 6152519565's, the 200-garbage probe.
    • Item 19 in two senses. Your request calls the capability check "#193 item 19". 6103860685 uses 19 for the installer-source rule.
    • inst:239 is ambiguous too. It reads "#193 (follow-ups, proposed): … (item 7); … (item 4); … (item 1)". Those are lane 2's item numbers from the table above it, and #193's own items 7, 4 and 1 are other things.
    • Fix: one comment on #193. It renumbers the repeats, or maps each one to its comment id, and says which item each of #196's citations means. No refold is needed.
  2. [low, the walk] gbs:168 (line 29) calls 5464938427 "lane 3's LAND".

    • That review is headed "lane openRepoTools-1": it is lane 1's independent review of #190 at dbc6837. gab:435 says so, and the walk's own :259–261 calls lane 1's reviews same-lane second opinions.
    • 3fd81c6 introduced this. Line 29's status does not depend on it.
    • Fix at the next touch: "lane 3's LAND 5464931249; lane 1's 5464938427".
  3. [low, the T010 record] tr:447 (finding 7 (i)) and tr:450 (finding 8's outcome-only check) say "not yet filed". Both were filed 40 seconds after 22bdb2e (16:35:23Z), as #193 items 16 and 17 in 6111225631 (16:36:03Z). Point to them, either at the next touch or in finding 9's #193 comment.

  4. [low, the PR body]

    • :5 says "This pull request carries no ready label". It has carried the label since 2026-10-13T18:53:08Z, and it was re-applied at 2026-10-17T20:46:41Z.
    • :33 and :166 say "The re-review of v3 is not recorded".
      • 5481368376 is lane 3's review of v3: its finding 1 measured the T3 change, and inst:72 cites it.
      • With finding 2's T015 item now named (inst:237), its condition for keeping the patch is met. This review records that.
    • :157 cites gab ":583 and :608". The gap sentence is :590; :583 is item 2's "PR #191, the code side, is open".
    • :16 gives the installer's earlier heads as 7668c51 and 3cca1e7, but 67a978c sits between them.

    If the body becomes the squash message, fix these before the squash. None of them is in a file.

  5. [low, the installer record] inst:212 cites lane 2's register note in brett-wip for "0 of 341 verdicts differ". Lane 2's citable copy is #186 6152491855, posted at 18:34:24Z, before 7428e78 (18:43:21Z). Cite it at the next touch.

  6. Clean: no host path, broken link or bad tally.

    • Host paths. HOST_ABSOLUTE_PATH (aaa7bc8:tests/test_repo_hygiene.py:2279–2285) finds 0 hits in the 12 files.
      • There is no /workspace/, /home/, /mnt/, /Users/ or private-prep path.
      • The /tmp/ lines are the five the body lists.
      • The one identifier-like string, a transcript id at gab:360, was already at 7c08f7e.
    • Links. All 26 relative Markdown links in the 9 .md files resolve in b50c83c's tree. The one false positive is the literal "](…)" at rg:170, which main already has.
    • Ids. All 38 review and comment ids in the delta's added lines and in the body exist on #186, #193, #190, #191, #195 or #196. The four run ids exist, on the heads named.
    • Whitespace. git diff --check a74c7fc b50c83c -- '*.md' exits 0, and none of the 12 files has a CR.
    • #191's landing. Every claim holds:
      • a74c7fc is the squash of 8246983, with parent f87fd5a;
      • its tree is d77e80b3, the same as 8246983's;
      • the installer is blob 7aa697c at cddd1f7, 8246983 and a74c7fc;
      • it landed on 2026-10-13.
    • The hold. gbs:48–55, cand:358–360 and body :167 match 6152410188 (18:29:47Z): what is held, what is not (#196 among them), and the order after the tag.

Since the delta (notes, not edits to #196)

  1. v1.0.0 is tagged (2026-10-14T23:17Z; v1.0.1 at 2026-10-15T14:20Z).
    • The hold text is dated and still reads correctly.
    • One phrase will age: cand:357, "Still owed: the full composed suite on this candidate". By the hold's order after the tag, the next composed run is on a candidate rebuilt after the pin bump, not on this one at 7f84ca4.
    • Say so in the next record. #196 needs no edit.
  2. #227 (aaa7bc8, 2026-10-15) removed tests-windows, parse-macos and tests-macos.
    • The code-leg patch no longer applies to main. The T010 code-leg patch's base, e27bc64, was main's tests.yml up to a74c7fc. On main's current tests.yml (3c7caa4), git apply --check fails at line 21.
    • Three passages describe jobs that are gone: tr:354, gbs:172 (line 33), and inst:236's "parse-macos covers the implementation only". Nothing in CI parses under bash 3.2 now.
    • These are evidence of their dates. T014 and T015 should regenerate the patch and restate the bash 3.2 point; a #193 item would hold it.
  3. #195's run 38447402302, on the merge with a74c7fc, ended with tests-macos failed and its other five jobs green. #227 has since removed that job. F2 stands until #195 lands (cand:269).

CI

  1. gh pr checks 196 (2026-10-17T20:48Z) exits 8, because jobs are pending.
    • The runs of 2026-10-13, on the merge with a74c7fc:
      • run 38450064954: tests succeeded (18:50:53Z–19:32:16Z), as did tests-no-submodule, tests-windows, parse-macos and guard-launch-mode;
      • run 38450378341 (ready): tests-macos passed, in 2h23m;
      • SonarCloud passed.
    • Today's run. ready was re-applied at 20:46:41Z, so run 39068152573 tests the merge with main aaa7bc8 (merge ref bff9026).
      • At 20:54:50Z, guard-launch-mode had passed.
      • tests and tests-no-submodule had been running since 20:46:46Z.
    • Your landing condition is that run's Linux tests job finishing green.

Not checked:

  • No suite was run, as briefed.
  • r3's build and its logs: they are in the private receipt. I went only as far as lane 2's cross-check.
  • The pass counts inside the CI job logs.
  • Lane 2's matrix: read, not re-run.
  • The brett-wip register note.
  • actionlint.

Lane: openRepoTools-3

@brettheap brettheap added ready landing gate: runs tests-macos once before the squash and removed ready landing gate: runs tests-macos once before the squash labels Oct 17, 2026
@brettheap
brettheap merged commit 983d8c7 into main Oct 17, 2026
20 checks passed
@brettheap
brettheap deleted the 004-migrate-to-triad-evidence-3 branch October 17, 2026 22:42
brettheap pushed a commit that referenced this pull request Oct 17, 2026
…e-pin-v1.0.1

Part of #186.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap pushed a commit that referenced this pull request Oct 17, 2026
Brings in #195 (aa14de3), #196 (983d8c7) and #212 (2732d59) on top of
aaa7bc8, so the T018 selection runs on the tree the PR will be judged
on. The only file both sides change is lanes-edit.sh: main's #212 edits
cleanup's temporaries and rename-lane's handoff stamp, the branch adds
project-legs and lane-reconcile's leg and paired-root reads. git merged
it with no conflict, the hunks are disjoint, and bash -n passes on the
result.

Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap added a commit that referenced this pull request Oct 19, 2026
…es, so the sweep, lane-end's gate, lane-handoff, lane-reconcile and the daily report can see a lane's work there, and change nothing a checkout with no project.yaml does (#246)

* docs: the triad split's lane-tooling side, surface by surface (T006, T007, T009, T010, T018)

docs/triad-lane-tooling-analysis.md: for every lane tool, the installer and
its receipt, the hooks, the manuals, the tests, CI and the brett-wip
register/inventory/handoff data, what the assembly/spec/code split of
opensoft/openRepoTools changes, at file:line on c4864ac, with the plan task
each change belongs to and whether it is a patch or a proposal.

No shipped command changes: no lane tool reads docs/, openspec/ or specs/
at run time (the adoption plan's cross-leg follow-ups for them are comments,
usage text, a URL or the object-key grammar), and every fix the triad does
need changes today's behaviour, so each is a proposal with a sketch. The
suite's test roots (T009/T010) are lane 1's PR #190 (c139e88); this branch's
earlier draft of them was withdrawn before commit, and the document records
the overlap, one gap and one measured defect in #190 for lane 1.

Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* WIP (untested): T018 worktree visibility for triad legs — the legs reader, the sweep's leg and paired-tree discovery, lane-end's leg rows

Checkpoint for an account swap, on the coordinator's word. NOTHING HERE HAS
BEEN RUN UNDER tests/run.sh: `bash -n` passes for lanes-edit.sh and lane-end,
and lane-worktrees parses as Python; one manual run of
`lanes-edit.sh project-legs` against openRepoShape's assembly-root
project.yaml template (pinned submodule) printed the spec and code legs, exit
0, with no workspace configured; 8 for a directory with no manifest; 64 for a
relative path.

DONE (T018, plan proposal 2 of docs/triad-lane-tooling-analysis.md §4.4):
- lanes-edit.sh: `project_leg_paths_file` (lane-start rung 5's awk, the
  seven deciding lines verbatim, printing every leg) and `assembly_legs`; the
  read-only subcommand `project-legs <checkout>` (0 rows, 8 none, 1 manifest
  unreadable, 64 usage), exempt from the workspace guard; listed in the
  unknown-subcommand refusal and the usage header.
- lane-worktrees sweep: where the lane's dir has a project.yaml naming legs,
  `Leg` rows for each leg and each initialized nested submodule (porcelain
  kind `leg`, a LEGS section, a summary suffix), counted only where the leg's
  HEAD's own Lane: trailer is this lane's and it holds work, or unreadable;
  every leg's worktree registrations enumerated; `<assembly>/worktrees`
  scanned as the paired root and claimed by the same HEAD-trailer rule as
  .claude/worktrees; legs' branches under --branches and probed under
  --fetch. No project.yaml: no helper call and no new code path's output.
- lane-end: `leg` porcelain rows counted (retire) or listed (not counted),
  "leg checkout(s)" in the gate's tally, no `--yes` offered when legs are all
  that is counted.

REMAINS, in order: (1) tests/test_triad_lane_tooling.py — assembly fixture
from the pinned template bytes with legs as real repositories and bare local
remotes, fake gh exiting 1; single-repository golden comparisons against
c45a452's tools (CI checks out depth 1, so the old bytes need a decision:
skip where c45a452 is absent, or captured goldens); the shared-awk identity
check against lane-start; (2) README-lanes.md and usage text for (a);
(3) commit (b) lane-handoff paired-root poll + recording the leg checkout,
lane-reconcile's leg registrations; (4) (c) the daily report's legs;
(5) (d) add recording the leg; (6) the shell suite once at the end.

Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* T018 (a): the triad guidance for the sweep and lane-end's gate — README-lanes, usage text, the dated §4.4 note; 2aa8a59's code now tested

Plan task T018 (#186), slice (d2), part (a). This
commit carries the guidance for the code 2aa8a59 added: a "A triad: its
legs, their worktrees and the paired trees (T018)" section and a `leg` row
in the porcelain block of docs/README-lanes.md; a "A TRIAD" paragraph in
`lane-worktrees --help`; four lines in lane-end's CLOSE-OUT GATE header
(`lane-end --help`); and a dated subsection at the end of §4.4 of
docs/triad-lane-tooling-analysis.md, including the note "outputs that change
regardless of shape" the coordinator asked for (the unknown-subcommand list
and usage header of lanes-edit.sh, and the --help texts), for lane 1's ruling.

THE INVARIANT: a checkout with no project.yaml declaring a leg other than
itself is asked nothing, so a single repository's sweep rows, gate verdict,
exit codes, register lines and inventory rows are unchanged. Only usage text
moves for every shape, as listed in the §4.4 note.

THE PROOF, run under tests/run.sh with this tree (tools as committed here and
in 2aa8a59):
  tests/run.sh -k "t018 or repo_hygiene or test_lane_worktrees"
  -> 1 failed, 299 passed, 1006 deselected in 1002.77s
The 299 include every test of tests/test_lane_worktrees.py and
tests/test_lane_worktrees_report.py, the hygiene module, and the T018 tests:
the single-repository golden comparison against c45a452's stored outputs and
the assembly cases (template bytes, legs as real repositories behind bare
local origins, fake gh exiting 1). The one failure was the hygiene check on
host-absolute paths, tripped by the golden fixture's `<ROOT>/home/projects/`;
that fixture and its test module are the next commit, WIP, with the fix.

Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* WIP (untested): T018 (a) tests — tests/test_triad_lane_tooling.py and the single-repository golden captured from c45a452's tools

Checkpoint for an account swap, on the coordinator's word. The module and
its stored fixture, after the fix of the one failure the previous run found;
the fixed form has NOT been run under tests/run.sh (the re-run,
`tests/run.sh -k "t018 or repo_hygiene"`, was stopped while still waiting
before the lock, exit 143, so it never held it).

DONE:
- The stored-fixture decision: the "before" of the single-repository
  invariant is tests/fixtures/t018/single-repository.golden, generated ONCE
  from c45a452's own tools (`git archive c45a452 | tar -x -C <dir>`, then
  `python3 tests/test_triad_lane_tooling.py --capture <dir>`), because CI
  checks out at depth 1 where c45a452 is not there to run. It covers add
  (dry run and act), the sweep's porcelain and table with --branches
  --include-scratch --include-caches, lane-reconcile, lane-end refusing and
  passing with the row and last log line, lane-handoff's output, top block
  and inventory, and the estate report, on a repository with a submodule that
  is no leg and on one whose project.yaml names no leg but itself.
- Normalised for what no two runs share and nothing else: the bench's root
  and $HOME (both spellings), tree ids, operation ids, stamps, hashes, ages,
  byte sizes, the order of the inventory's sidecars (their file names are a
  checksum of the bench's path), and the sweep table's note about this
  account's unreadable /proc processes (a host fact). The golden test runs on
  Linux only, where it was captured.
- The fix since the run: `$HOME` is normalised to `<HOME>` before the root,
  so the fixture names no `/home/<user>/` path (the hygiene failure). Captured
  twice from c45a452 afterwards: byte-identical. The current tools' outputs
  on the same scenario equal it (compared by the module's own functions,
  not by pytest).

REMAINS: run `tests/run.sh -k "t018 or repo_hygiene"` and record the counts;
then (b) lane-handoff's paired-root poll and leg-checkout record with
lane-reconcile's leg registrations, (c) the report's legs, (d) add's leg
record (all three drafted in the session scratchpad, not applied), and the
shell suite once at the end.

Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* WIP (untested): T010 docs — the §4.14 CI sketch exits 4 under #190's composed mode; three ways a composed job can pin the PR's code

Plan task T010 (#186). Queued by the coordinator from
the review of #190: the §4.14 sketch (code-PR CI with
OPENREPOTOOLS_COMPOSED=1 against an assembly checked out at main) would exit
4, because #190 at dbc6837 refuses any code root the assembly does not pin —
Roots.absent() (tests/conftest.py:283) runs _leg_at_its_pin (:259), and
pytest_configure (:404-412) raises it as pytest.UsageError — while
discover_assembly (:159-172) finds only an assembly that mounts the code
root. Dated notes in §4.14 and §7 item 7: the composed job must check out an
assembly whose code gitlink points at the PR's own commit, or a throwaway
assembly built for the run that pins it, or use #190's standalone mode.

Documentation only; no tool or test changes. Marked WIP (untested) because
the hygiene suite has not been run over this text: the checkpoint came first.

Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* T018 (c): the daily report counts each leg of an assembly as a checkout of its own, with a legs section only where an assembly's leg is checked out

Plan task T018 (#186), proposal 3 of
docs/triad-lane-tooling-analysis.md §4.4. A leg is a submodule, its `.git` a
FILE, so `Report.build` never counted one as a canonical checkout and the
branches all lane work makes after the split never reached the report. Now,
for every canonical checkout whose own project.yaml declares legs (read by
`lanes-edit.sh project-legs`), each checked-out leg is read as a checkout of
its own: its branches ("Unmerged branches…"), its root `main` ("Root main
divergence") and its rescue branches ("Awaiting disposition"), each leg row
ending with `the <path> leg of <assembly>`; and a section "Assembly legs with
uncommitted or unpublished work" lists the legs holding dirty or untracked
paths or a HEAD no origin ref holds, or that could not be read. A manifest
that cannot be read is a row of "Records the report could not read". Guidance
in docs/README-lanes.md's report table and `lane-worktrees --help`.

THE INVARIANT: a canonical checkout with no project.yaml is asked nothing; the
legs section is added only where a leg was found, so a report of single
repositories is byte for byte what it was. This changes the daily report for
the estate's existing triads (IRRS, IRSS, MedxEHR, openDox), which is what
proposal 3 is for.

THE PROOF:
  * For 8e2227e and 96da9af, which were WIP: `tests/run.sh -k "t018 or
    repo_hygiene"` -> 162 passed, 1144 deselected in 134.24s (2026-10-09T13:25Z).
  * For this commit: run.sh's own in-lock command under the same lock, because
    the wrapper's workstation-wide census had waited 30 minutes on other
    projects' pytest runs (#184) - on the coordinator's word, the form lane 1
    approved on 2026-10-07: `flock -w 7200 /tmp/openrepotools-pytest.lock env
    TMPDIR=<run root>/tmp python3 -m pytest tests -q -p no:cacheprovider
    --basetemp=<run root>/basetemp -k "t018 or repo_hygiene or
    test_lane_worktrees_report"` -> 184 passed, 1124 deselected in 457.65s.
    That includes the single-repository golden (the report section of c45a452's
    stored outputs, unchanged), every test of tests/test_lane_worktrees_report.py,
    the hygiene module, and two new cases: an assembly whose code leg holds an
    unpushed branch and whose spec leg is dirty, and an estate with no assembly,
    which has no legs section.

Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* T018 (d): lane-worktrees add records the leg it made a tree from, and says of a tree made from an assembly itself that its legs are empty there

Plan task T018 of #186, slice (d2), proposal 1 of
docs/triad-lane-tooling-analysis.md §4.4.

Where the lane's checkout is an ASSEMBLY - its project.yaml declares a leg
other than itself, read only through `lanes-edit.sh project-legs`, lane-start's
rung-5 reading - `lane-worktrees add` matches the checkout it makes the tree
from against each leg by their common git directory (the leg's own toplevel
must be the leg's path, so an uninitialized leg, which git answers as the
assembly, never matches). A tree made from the leg, or from any worktree of
that leg's repository (a paired <assembly>/worktrees/<feature>/code included),
is recorded in the #97 inventory with THE LEG as its checkout, which every
inventory reader enumerates; the dry-run and success notes name the leg, and
the success note says "with <leg> as its checkout" where the checkout given was
a worktree of it. A tree made from the assembly itself is made as before, and a
note says a worktree does not check the legs out and names the
`--checkout <assembly>/<leg>` that makes a leg's tree. A manifest that cannot
be read refuses, exit 2, nothing made (Amendment 7(d)).

THE INVARIANT: a lane whose checkout has no project.yaml is asked nothing (no
helper call), and a manifest that declares no leg other than `.` answers 8 -
so on a single repository `add`'s output, exit code, inventory row and
take-back text are byte-for-byte what they were. PROOF:
test_t018_single_repository_outputs_are_c45a452s_byte_for_byte compares the
current tools against tests/fixtures/t018/single-repository.golden, stored from
c45a452's tools, whose "add --dry-run", "add" and "add (manifest, no leg)"
sections are add's; it passes unchanged. The shell suite's
#163 add block (tests/test_lane_helpers.sh) runs once on the merged tree at the
end of this slice.

Help text that changes regardless of shape (listed in the §4.4 note for lane
1's ruling, item 2): `lane-worktrees --help`'s "A TRIAD" paragraph gains two
sentences on add. Guidance: docs/README-lanes.md "In a triad (T018), a tree of
a leg is made from the leg". The §4.4 note's first paragraph now names (a)
through (d), with (b) a second writer's branch this one merges.

Tests (tests/test_triad_lane_tooling.py, fixtures from openRepoShape's
assembly-root template bytes, legs behind bare local remotes, gh exits 1):
test_t018_add_records_the_leg_it_made_a_tree_from (dry run, an add from
<assembly>/code, an add from a paired worktree recorded under the leg, the
sweep then reading that tree through its leg) and
test_t018_add_from_the_assembly_itself_says_its_legs_are_empty.

COUNTS: `tests/run.sh -k "t018 or repo_hygiene or test_lane_worktrees_report"`
on this tree: 186 passed, 1124 deselected in 212.76s (0:03:32), exit 0
(30f9dc0's same selection: 184 passed; the two new (d) tests make 186).
run.sh's own census and lock (it waited ~48 minutes behind other runs;
no flock fallback was used). The selection holds no supervised-restart
test.

Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* T018 (b): lane-handoff sees a triad's legs — the paired trees polled, each tree recorded under its leg

Plan task T018 (#186), slice (d2), part (b), first
half. Two gaps docs/triad-lane-tooling-analysis.md §4.4 and §4.7 measured in
the handoff's writer poll, closed SHAPE-GATED through 2aa8a59's shared reader
(`lanes-edit.sh project-legs`, called as it is):

(1) THE PAIRED ROOT IS POLLED. Where the lane's checkout is an assembly (its
    own project.yaml declares a leg other than itself), the handoff polls
    `<assembly>/worktrees/<feature>` and `<feature>/<each declared leg path>`
    as it polls `.claude/worktrees`: every directory there with a `.git` of its
    own (anything else under it is the assembly's working tree, where git
    answers too). A lane with unpushed work in a paired tree used to be absent
    from its WRITERS block.
(2) EACH TREE IS RECORDED UNDER ITS LEG. A polled tree whose repository's
    common git directory is a checked-out leg's is filed with that leg
    (project-legs' third column, the path `add --checkout` records) as its
    checkout. Anything else keeps the lane's dir, as before. The first handoff
    used to rewrite the leg that `lane-worktrees add --checkout <assembly>/code`
    recorded to the assembly. A leg that is NOT checked out is passed over:
    git answers the assembly there, and the drafts' leg map, which lacked that
    guard, would have filed every tree of the assembly under a deinitialized
    leg (measured: with the guard deleted, the deinit case fails).

AN UNREADABLE project.yaml is never "no legs" (Amendment 7(d)), and the swap
still completes (R-A11-11). The paired root is not polled, the top block's
WRITERS section carries a `- NOT POLLED:` line, no sidecar is filed for a
tree that is not of the lane's own repository (the checkout `add` recorded
is kept), and the lane stays SWAPPING.

THE GATE: a checkout with no project.yaml is asked nothing. There is no
helper call and no git read, and set-lane-tree gets the same argv. One whose
project.yaml names no leg but itself costs one `project-legs` call, which
answers 8, and nothing else. `--supervise` and `--restart-status` exit before
any changed line. No usage or --help text moves.

THE PROOF, under tests/run.sh, lanes-edit.sh at base (lane-reconcile is the
next commit):
  tests/run.sh -k "t018 or supervised_restart or repo_hygiene or openrepotools_command" -rs
  -> 7 failed, 429 passed, 874 deselected in 3149.31s (load 60-70 on 32 cores)
  The 429 include tests/test_triad_lane_tooling.py, whose single-repository
  golden from c45a452 passes UNREGENERATED (tests/fixtures/t018/ untouched),
  and the new tests/test_triad_lane_tooling_handoff.py: three assembly cases
  built from openRepoShape's template bytes at the pin (legs as real
  repositories behind bare local origins, fake gh exiting 1), and a second
  stored golden, tests/fixtures/t018-handoff/single-repository-decoys.golden.
  That golden was captured twice from c45a452's own tools, byte-identical,
  on a single repository laid out to look like a triad: a submodule's
  worktree under worktrees/<feature>/code, and a tree recorded with the
  submodule as its checkout. Its scenario has no project.yaml, and then one
  naming no leg but itself. The handoff output, top block, inventory and
  lane-reconcile before and after all match it.
  The 7 failures are --restart/--supervise timing cases of
  tests/test_supervised_restart_regressions.py (TimeoutExpired, "observer
  never confirmed readiness"). A/B at load ~40, one run.sh run each:
    this tree:       -k "<the 7>" -> 7 passed, 1303 deselected in 143.69s
    96da9af (archive copy, no change): -> 7 passed, 1299 deselected in 150.59s
  so they are load, not this change.
  Against 96da9af's lane-handoff the three new assembly cases fail
  (measured); the base handoff turns the `add`-recorded `<assembly>/code`
  into `<assembly>`.

Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* T018 (b): lane-reconcile reads a triad's legs — each leg's worktree registrations and the paired root

Plan task T018 (#186), slice (d2), part (b), second
half: the third gap docs/triad-lane-tooling-analysis.md §4.4 measured.
`lanes-edit.sh lane-reconcile` read only the checkout's own worktree
registrations and the two lane roots, so a tree of a leg's repository that no
sidecar names was invisible to it. SHAPE-GATED, through `assembly_legs`
(2aa8a59's shared reader, called as it is):

- EVERY CHECKED-OUT LEG'S REGISTRATIONS, read beside the checkout's own. Each
  worktree git registers in a leg that no sidecar names is a TREE row:
  `unmanaged`, or `stale-registration` where its directory is gone. The row
  names the leg ("git registers it in <leg>, the code leg of
  <assembly>/project.yaml (<repository>)"). The leg's main entry, which git
  spells as its git directory under `.git/modules/`, is skipped by place. A
  leg that is not checked out (git answers the assembly there) registers
  nothing of its own and is passed over.
- THE PAIRED ROOT, after the lane roots: `<assembly>/worktrees/<feature>` and
  `<feature>/<each declared leg path>`, each holding a `.git` of its own that
  nothing above named (a lost registration, or a clone where a worktree
  belongs), is `unmanaged`.
- PATHS NOBODY READ ARE NOT CLEARED: a project.yaml that cannot be read, a
  leg whose `git worktree list` fails, or a paired root or feature directory
  that cannot be listed makes the verdict `indeterminate`. The unreadable
  manifest is said in the words `lane-worktrees`' sweep refuses with ("the
  legs of the assembly <dir> could not be read"), because the sweep reads
  this verdict first and refuses on it. The previous run's one failure,
  tests/test_triad_lane_tooling.py:616, was exactly that: same exit 2, the
  reconciliation's wording instead of the sweep's. One fact now has one
  spelling, and that test passes unchanged.

Nothing is adopted, deleted or overwritten, as before. THE GATE: a checkout
with no project.yaml is asked nothing. One whose project.yaml names no leg
but itself gets `assembly_legs`' 8 and nothing else. Every row, count and
verdict on a single repository is what it was.

THE PROOF, under tests/run.sh, this exact tree (lanes-edit.sh and the test
module last written 2026-10-10T02:43:15Z, the run started 02:43:34Z):
  tests/run.sh -k "t018 or supervised_restart or lanes_index or repo_hygiene or lane_start_claude_current or openrepotools_command" -rs
  -> 544 passed, 768 deselected in 1318.62s (0:21:58), on a quiet box
  The 544 include both T018 modules. Two goldens stored from c45a452's tools
  pass UNREGENERATED: tests/fixtures/t018/single-repository.golden (byte
  identical) and tests/fixtures/t018-handoff/single-repository-decoys.golden.
  The latter covers lane-reconcile before and after a handoff on a
  repository laid out to look like a triad. Also included are the
  supervised-restart module, the lanes-index module (the real
  lane-reconcile), lane-start's module (it calls lane-reconcile), hygiene
  and the installer module. Two new cases are in
  tests/test_triad_lane_tooling_handoff.py, on openRepoShape's template
  bytes at the pin: the legs' registrations and the paired root are
  reported (a spec tree made away from every root, a paired code worktree,
  a clone in the paired root, a stale registration; neither leg nor its
  `.git/modules` entry reported), and the indeterminate verdicts (unreadable
  manifest, unlistable paired root). Against 96da9af's lanes-edit.sh both
  fail (measured).
  The earlier run of this half, before the wording fix: 1 failed, 446
  passed, 865 deselected in 2718.97s (the :616 case above).

Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* WIP (untested): T018 (b) guidance — README-lanes says what lane-handoff and lane-reconcile read in a triad, writer 2's draft checked against 9a5bbe2

Writer 2's paragraph (briefs/t018-b-guidance.md, 2026-10-09T21:53Z) after
"`lane-end`'s gate" in the T018 section, each sentence checked against the
merged lane-handoff (4e39854) and lane_reconcile (9a5bbe2). Two corrections:
the unreadable-manifest verdict is spelled in the sweep's words, which the
sweep reads first and refuses on (exit 2); and a project.yaml naming no leg
but itself is asked one project-legs call (answering 8), not nothing.

Untested: committed for an account swap while the merged tree's scoped run
was in flight; that run's counts follow in the next commit.

Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* WIP (untested): T018 (d) — add's refusal on an unreadable manifest is tested, and spelled as the sweep and lane-reconcile spell it

test_t018_add_refuses_where_the_manifest_cannot_be_read holds 13cb52d's README
claim (mode-000 project.yaml: exit 2, no tree, no inventory row). The refusal
reads "the legs of the assembly <dir> could not be read", the sweep's and
9a5bbe2's words, in place of "the legs of the lane's assembly <dir>". Reached
only where <lane dir>/project.yaml is there and project-legs fails on it; a
checkout with no project.yaml is asked nothing.

Untested: committed for an account swap while the merged tree's scoped run
was in flight; that run's counts follow in the next commit.

Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* T018 docs: a parked paired tree is not the lane's to the sweep (§7 item 11, for lane 1's ruling), and §6 says what the branch now ships

docs/README-lanes.md, T018 section: a parked or resumed paired tree carries no
Lane: trailer (park.sh puts the lane in the WIP subject only; resume
soft-resets those commits), so the sweep and lane-end's gate count it for
nobody unless the lane's inventory names it. The paragraph says what to read
before lane-end in an assembly, and how to record such a tree with
set-lane-tree so the gate counts it.

docs/triad-lane-tooling-analysis.md: a dated §6 note on what the branch ships
beyond its first commit, and §7 item 11, the choice between reading park's
WIP subject and giving park's commit a trailer, plus whether resume records
the tree it rebuilds.

Written by the previous writer on 2026-10-11 and left uncommitted; checked
here against HEAD 8cb1368, whose lane-worktrees, lane-end and lane-handoff are
byte-identical to 9991d69's. Every line reference holds: lane-worktrees
:2424-2431, :4473-4494 and :2124, lane-end :2122-2127, the FOREIGN reason text
(lane-worktrees:3037-3039), and workBenches ffd5516 park.sh :226 and :860 and
resume.sh :221-231 and :746. Three corrections in item 11: the source is
review 5481327496 on #196 (a pull-request review, not an issue comment); its
finding 2 was derived from code and not run, so "measured" is now "derived",
the document's own word for that; and the inventory names a paired tree through
lanes-edit.sh set-lane-tree, not through lane-worktrees add, which makes its
trees under the lane's own root only.

Docs only. No test run for this commit; the branch's one verification run
follows the M1(a) and should-fix commits.

Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* T018 (b), pre-PR review M1 option (a): lane-handoff polls the paired root by the sweep's rule, so one lane's handoff never takes another lane's feature trees; and S1, S5, N1-N5

M1 (a). In an assembly, lane-handoff polled and recorded every .git-bearing
tree under <assembly>/worktrees as the running lane's writer. The paired root
is per feature, not per lane, so after one handoff another lane's paired
work was this lane's inventory: lane-end refused on it, sweep --yes could
rescue and remove it, and the next session was told to relaunch writers into
it. Now a paired tree is polled and recorded only where this lane's inventory
names it (lanes-edit.sh lane-trees, read once), its HEAD's own last Lane:
trailer is this lane's on a commit origin's default branch lacks (a bash
mirror of lane-worktrees' own_trailers/head_lane_trailer; a clone is never
claimed by trailer, as the sweep never adopts one), or the --writers-file
names it. Every other checkout there is one "- PAIRED ROOT, not this lane's:
<path> — branch …" line under its own heading in the top block, outside
WRITERS, not counted and not recorded. Pending lane 1's ruling on #186
between (a) and (b).

S1. A paired root, feature directory or leg directory that cannot be listed
or searched was skipped in silence by the handoff. It is now a "- NOT
POLLED" line, a stderr note and a lane left SWAPPING, as is an inventory
that cannot be read. lane-reconcile (indeterminate) and the sweep's _paired
(exit 2) gain the one case they also passed over: a leg's directory under a
feature that cannot be searched, and in the sweep a root or feature
directory that can be listed but not searched.

N1. lane-reconcile passes the feature directory to awk through ENVIRON, not
-v. N3. The handoff's unreadable-manifest words are the sweep's and
lane-reconcile's ("the legs of the assembly <dir> could not be read").
N4. add's hint names every leg's --checkout, not legs[-1]. N5. The handoff
keeps a recorded checkout of the tree's own repository (a dependency nested
in a leg) instead of re-recording the tree under the assembly.

Tests. N2: the assembly cases wear conftest's NEEDS_UPSTREAM (composed mode
fails rather than skips), and the no-assembly report test needs no marker.
New: the two-lane M1 regression; S1's three unlistable directories (handoff
and reconcile); N5; S5(b) the sweep's absent row and add's toplevel guard on
a deinitialized leg; S5(c) an unreadable leg (counted, lane-end refuses),
the submodule-status refusal (unshadowed by reconcile) and _paired's own
refusals called directly; and the coordinator's quoted-manifest case
(openRepoShape v1.0.0, #395 writes path: "{{SPEC_PATH}}"): project-legs
already strips one pair of " or ' quotes, and the test holds it for both
quote styles. Two existing handoff tests change for M1 (a): the no-commit
paired spec tree is named by a writers file, and the paired code tree of the
deinitialized-leg case carries the lane's trailer.

A checkout with no project.yaml is untouched: every new path runs only where
project-legs answered legs (lane-handoff, lane-reconcile, the sweep) or the
manifest declares them (add).

Untested in this commit: the branch's one verification run follows on the
final tree, and its counts are in the commit that ends this step.

Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* T018 docs: README-lanes and the analysis say what M1 (a), S1 and S2 changed; the analysis's false statements about the branch are dated and corrected (S4)

docs/README-lanes.md, T018 section:
- M1 (a): "each lane's handoff records every paired tree" is gone. The
  handoff polls and records a paired tree only where the lane's inventory
  names it, its HEAD's own Lane: trailer is the lane's on a commit origin's
  default branch lacks, or the writers file names it; every other one is a
  "PAIRED ROOT, not this lane's" line outside WRITERS, not counted or
  recorded. The sweep's bullet says the handoff no longer widens its
  paired-root rule, and the parked-tree paragraph says a handoff does not
  record a parked tree for the lane.
- S1: the handoff's NOT POLLED, stderr note and SWAPPING for an unlistable
  paired root, feature directory or leg directory, or an unreadable
  inventory; reconcile's indeterminate and the sweep's refusal now also
  cover a leg directory that cannot be searched.
- S2: "no --yes acts on a leg" is now "on a leg checkout": under --branches
  a leg's branches are rows like the checkout's own, and --yes deletes a
  merged lane branch there as it does in the checkout, bundling first.
- N5: a tree recorded under a checkout of its own repository (a dependency
  nested in a leg) keeps that checkout through a handoff.

docs/triad-lane-tooling-analysis.md (S4): dated 2026-10-13 notes at §0 item 1
and §6. "This branch carries no test change of its own" was true of its first
commit only; it now carries two test modules and two goldens and still
changes none of the suite's roots. §6 records M1 (a) and S1, and §7 item 11
says the handoff no longer closes the parked-tree gap by itself.

Verification, one run through tests/run.sh on this commit's tree, 1511f94
(4d4957d plus these docs; main f87fd5a merged at 8cb1368), 2026-10-13
19:10-19:40Z. Every module but the four below was --ignore'd, with
-k "triad_lane_tooling or repo_hygiene or test_the_lane_helper_suite_passes":
  181 passed, 1 failed, 1 deselected in 1818.96s.
  - tests/test_triad_lane_tooling.py, tests/test_triad_lane_tooling_handoff.py
    (both goldens unregenerated) and tests/test_repo_hygiene.py: all passed.
  - The shell suite, tests/test_lane_helpers_suite.py::
    test_the_lane_helper_suite_passes: 4598 passed, 2 failed. Both are
    lane-start row-stamp assertions, "…with clause (c)'s directory and
    window in the tail" and "the row's stamp states clause (c)'s two facts
    … in its TAIL". lane-start's cut_to_line caps the stamp at 230 characters
    (lane-start:3502-3510). Under run.sh the suite's $HOME is
    ~/.local/state/openRepoTools/tmp/<UTC>-<pid>/tmp/tmp.XXXXXXXXXX/home, and
    the stamp then reaches 231 characters before its window value, so the
    tail is cut ("…/home/projects/repoA; ..."). With a /tmp-length home it
    is 165. This branch changes none of lane-start, tests/test_lane_helpers.sh,
    tests/test_lane_helpers_suite.py or tests/run.sh (git diff f87fd5a is
    empty for all four), so the two failures do not come from it.
An earlier attempt (run1) ran nothing: zsh passed the --ignore list as one
argument and pytest exited 4.

Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* WIP (untested): T018: lane-reconcile reports a leg whose .git git cannot read as the sweep does - a TREE row `unreadable` that requires recovery - instead of passing over it in silence

lanes-edit.sh, lane_reconcile, the legs' registrations: a declared leg
whose git toplevel is not the leg itself was skipped whether or not a
.git stood in it, so a leg with a .git of its own that git cannot read
(a gitfile naming no repository, or a .git where git answers the
assembly) said nothing, while the sweep's `leg` row for it is
`unreadable` and counted (exit 3, lane-end refuses). It now takes the
sweep's rule (probe_leg: toplevel not the leg, and a .git stands there)
and the sweep's word: one TREE row `unreadable`, in the sweep's words
("git could not be read through it (it has a .git of its own and git
could not read it: <git's answer>)"), counted under "require recovery",
so lane-start's recovery read counts it too. A leg with no .git (not
checked out) is passed over as before, and a path a sidecar already
names is not reported twice.

The verdict is not changed: the sweep counts that leg and goes on, and
an `indeterminate` here is what the sweep refuses on (exit 2) before it
reads the legs, which would hide its own count and break
test_t018_a_leg_git_cannot_read_is_counted_and_holds_lane_end.

tests/test_triad_lane_tooling_handoff.py: a triad whose spec leg's .git
is a gitfile naming no repository, then an empty .git directory: one
`unreadable` row naming the leg, 1 require recovery, the verdict
ungraceful-stop, and the sweep still exit 3 with the leg `unreadable`
and counted; readable again, and deinitialized, the leg names nothing.

docs/README-lanes.md (T018 section): one sentence for the row.

A checkout with no project.yaml is untouched: the block runs only where
project-legs answered legs (lrc_lgrc 0).

lane-handoff's own leg loop (lane-handoff:2093-2095) skips such a leg
in the same way; it is not changed here.

Untested: the one verification run (run3, on this tree 2f23887) waited in
the shared pytest lock queue from 20:50Z and was stopped at 21:21Z, before
it took the lock, for the account swap; no count was taken.

Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* WIP (untested): T018: lane-handoff counts a leg git cannot read as unreadable, as the sweep and lane-reconcile do; verification not run

lane-handoff, the legs' common directories (was lines 2093-2095): a
declared leg whose git toplevel is not the leg itself was skipped whether
or not a .git stood in it. So a leg with a .git of its own that git
cannot read (a gitfile naming no repository, or a .git where git answers
the assembly) said nothing, while the sweep's `leg` row for it is
`unreadable` and counted and lane-reconcile's TREE row (9f23a6b) is
`unreadable` under "require recovery". Worse, a tree `lane-worktrees add
--checkout <leg>` made from that leg was re-recorded under the ASSEMBLY
at the handoff, over the checkout `add` recorded.

It now takes the sweep's rule (probe_leg: toplevel not the leg, and a
.git stands there) and the sweep's words: one `- UNREADABLE LEG:` line in
the top block's WRITERS section ("`<leg>` is unreadable, the <decl> leg
of <dir>/project.yaml (<repo>): git could not be read through it (it has
a .git of its own and git could not read it: <git's answer>)"), a note on
stderr, and a count (`legs: N of the assembly's legs under <dir>
unreadable`). As lane-reconcile keeps its verdict for it, the lifecycle is
not held SWAPPING for the leg itself: the poll looked everywhere it
looks. writer_checkout files no guessed checkout while a leg is
unreadable: a tree of no readable leg's repository, not of the
assembly's own and recorded under no checkout of its own repository is
named and NOT recorded, so the leg `add` recorded stays, and the lane
stays SWAPPING for that tree (the existing "no checkout can be vouched
for" rule). A leg with no .git (not checked out) is passed over as
before. A checkout with no project.yaml, or one naming no leg but
itself, never runs the leg loop: single-repository output is unchanged.

docs/README-lanes.md: the handoff paragraph says so.

tests/test_triad_lane_tooling_handoff.py:
test_t018_the_handoff_reports_a_leg_git_cannot_read_as_the_sweep_counts_it,
beside the reconcile one: the spec leg's .git a gitfile naming no
repository, then an empty .git directory: one UNREADABLE LEG line, the
note and the count, the lane SWAPPED, the reconciliation `unreadable`, 1
require recovery and `resumable`; a tree `add` made from the spec leg
keeps the spec leg as its checkout, named and not recorded, the lane
SWAPPING; readable again, nothing named and SWAPPED. The deinitialized
leg test now also holds that a leg with no .git is never `unreadable`.

VERIFICATION NOT RUN. The one run of the T018 selection (verify.sh, run4.log,
head 9f23a6b, index tree 0b691c8) waited for the shared pytest lock
behind other lanes' runs from 21:59:32Z and ended exit=143 (a SIGTERM
this writer did not send) at 22:26:46Z before any test ran; an account
swap was called at 22:20:05Z. So this commit and 9f23a6b
are both UNTESTED. Before this commit: bash -n lane-handoff is clean,
the test file parses. The next run on this tree verifies both.

Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* T018: lane-handoff withholds only a tree that could be an unreadable leg's, names a tree git cannot read, and says what it withheld (review of bf2a220, S1, S2, N1-N4)

S1. lane-handoff, writer_checkout: while a leg was unreadable, every
tree not of the assembly's own repository was withheld, a worktree of an
unrelated repository in the lane's own root included, so the lane stayed
SWAPPING on every handoff and those records went stale. Now a tree is
withheld only where it could be of that leg's repository: its common git
directory cannot be read, or lies physically inside the assembly or its
git directory (.git/modules/<leg>, a leg's own .git) without being the
assembly's own, or the inventory records it under an unreadable leg. Any
other tree keeps the lane's dir, as when every leg reads.
Test: test_t018_an_unreadable_leg_withholds_no_tree_of_another_repository
(a worktree of a third repository, recorded before and made after the
leg broke; and one a record names under the unreadable leg).

S2. lane-handoff, poll_writer: `git rev-parse --git-dir || return 0`
dropped any tree whose .git git cannot read, so a damaged
.git/modules/<leg> took every tree of that leg out of WRITERS and the
count while the lane went SWAPPED. In an assembly (legs_state set) such
a tree is now listed and counted, UNREADABLE and (NOT recorded), added to
lc_inventory_missed, and named on stderr. A checkout with no project.yaml
naming a leg passes over it as before, so the goldens do not move.
Test: test_t018_a_tree_git_cannot_read_in_an_assembly_is_named_and_holds_the_lane_swapping
(modules/spec deleted after add --checkout spec, with a paired spec tree
the inventory names; and a single repository's tree, passed over).

N1. The UNREADABLE LEG line's clause now depends on whether any tree was
withheld ("the N tree(s) this handoff could not place are named above,
each marked (NOT recorded)", or "every tree this handoff polled could
still be placed, so none is withheld for it"), and a withheld tree's
WRITERS line is marked (NOT recorded). The leg rows are kept as
<leg> TAB <why> and written by top_block after the poll.
N2. lane-handoff's UNREADABLE LEG line and note, and lanes-edit.sh's
lane-reconcile TREE row for an unreadable leg, say "before relaunching
a writer onto a tree of its repository": a leg is the assembly's.
N3. docs/README-lanes.md says the leg count is stderr's `legs: N ...`
line, never WRITERS' (N found), and describes S1, S2 and N1.
N4. Test: test_t018_a_leg_tree_add_made_is_withheld_where_the_legs_git_is_an_empty_directory
(the empty .git directory, where git answers the assembly in the leg,
with a tree add made from it).
The existing handoff and reconcile unreadable-leg tests now also assert
N1 and N2.

bash -n passes on lane-handoff and lanes-edit.sh; the added lines use no
declare -A, ${x,,}, mapfile, readlink -f, <<< or [[.

Authoring smoke check only (the one verification follows on the final
head): the three new tests, the two changed ones and the handoff golden
test, each called once outside pytest with LANES_* and TMUX* unset, all
passed; the three new tests each failed against cf7db4a's tools, on the
withheld third-repository tree, the writer count and the (NOT recorded)
marker respectively.

Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* T018 docs: record lane 1's rulings of comment 6240800023 in the analysis's §7 (items 3, 11 and a new 12 for M1)

docs/triad-lane-tooling-analysis.md §7: a dated head note naming
comment 6240800023 on #186 (posted 2026-10-17T20:48Z), and each ruling
at its item:
- item 3: proposal 1 (the guidance) goes live now; proposals 2 and 3 go
  live with the T018 PR, on the condition of the read-only before/after
  measurement over the triads (IRRS, IRSS, MedxEHR, MedxGlass, openDox,
  openXdox, openWallet and the codexFactory copies), main's tools against
  T018's, and any verdict that flips to a refusal told to its owning
  lane before the landing;
- item 11: (ii), park's WIP commit carries a `Lane: <lane>` trailer, and
  resume records the tree it rebuilds with `lanes-edit.sh set-lane-tree
  <lane> <path> --checkout <assembly>/<leg>`; both workBenches changes,
  one workBenches issue linked from the T018 PR ("workBenches issue: to
  be linked"); the PR does not wait for it;
- item 12, new: the pre-PR review's M1, (a) as implemented in 4d4957d,
  with the two-lane regression test
  (test_t018_a_handoff_takes_no_other_lanes_paired_tree).
§6's 2026-10-13 note now says the M1 ruling was made, and where.

docs/README-lanes.md: the two T018 sentences that called these rulings
pending now say they were made, and the parked-tree check before
lane-end stands until the workBenches changes land.

Docs only.

Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* T018: the daily report lists a leg that is also a canonical clone once (measurement finding 1)

lane-worktrees, the report's _branches, _root_main and _aging iterated
`canonical + [g.path for g in legs if g.checkout]`. A leg whose .git is
a directory (a clone, not a submodule's gitfile) is also canonical, so
it was read twice: the read-only triad measurement of 2026-10-17
(finding 1) saw 8 branch rows printed twice on this workstation, and
_root_main would do the same for a leg whose main is ahead. _aging's
`seen` set hid it there only where the two spellings matched.

All three now read Report._checkouts(canonical): the canonical
checkouts, then the checked-out legs, each once by os.path.realpath,
first occurrence kept. With no leg checked out it returns canonical
untouched, so a single-repository report cannot move. _leg_cell also
matches a leg by real path, so the row kept (the canonical spelling)
still names its leg.

Test: test_t018_the_report_lists_a_leg_that_is_also_a_canonical_clone_once
(an assembly whose project.yaml declares two legs that are clones: the
code leg's unpushed branch, its aged rescue branch and the spec leg's
main ahead of origin each appear once, each naming its leg). It needs
no template. An authoring smoke check outside pytest: it passes on this
tree and fails on 69f0017's lane-worktrees, on the duplicated branch row.

Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* T018 docs: the analysis spells bench paths ~/projects/..., as the hygiene rule of #229 item 22 asks

f595e44 made tests/test_repo_hygiene.py refuse a committed path that
starts at the bench root, except inside a single-quoted grep pattern.
docs/triad-lane-tooling-analysis.md had seven such lines (§0, §4.2,
§4.5, §4.9, §4.12 and two rows of §4.15). Each now says ~/projects/...,
the bench's projects directory, as f595e44 respelled the same path in
ideation/; the §4.15 grep pattern keeps the spelling it searched for,
under the rule's allowance. A dated note under the header says so.
host_absolute_path_hits over every file this branch adds or changes
now finds nothing.

Docs only.

Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* T018: verify 88ee9d9: 224 passed, 1 failed (test_the_lane_helper_suite_passes: two #180 row-stamp cuts, main's own); selection triad_lane_tooling or repo_hygiene or test_the_lane_helper_suite_passes

Log: briefs/t018-verify-20261018T034203Z.log (lane openRepoTools-3's
briefs directory), through briefs/verify.sh and tests/run.sh, with every
LANES_* and TMUX* variable unset.
  start 2026-10-18T03:42:03Z head 88ee9d9 tree-of-index 07fa62e
  1 failed, 224 passed, 1 deselected in 4045.64s (1:07:25)
  exit=1 end 2026-10-18T05:34:13Z
The run queued behind other lanes' pytest from 03:42Z and took the
lock at about 04:26Z.

Every test of tests/test_triad_lane_tooling.py,
tests/test_triad_lane_tooling_handoff.py and tests/test_repo_hygiene.py
passed, both goldens unregenerated among them.

The one failure, tests/test_lane_helpers_suite.py::
test_the_lane_helper_suite_passes: the shell suite ended "4620 passed,
2 failed, 0 skipped, 0 pending". Both FAILs are lane-start row-stamp
tail cases of #180's group:
- log line 100, "...with clause (c)'s directory and window in the
  tail, for the person reading the row" (repoA): the cell line is cut
  to "...home/projects/repoA; ..." (228 chars), so "; window " is gone;
- log line 1967, "...having found the checkout by its real directory
  name, not the typed one" (repoCase): the cell line is cut to
  "...no launch; dir ..." (123 chars), the whole path dropped.
Cause: lane-start's cut_to_line caps the state line at 230 characters,
cuts there and trims back to the last space, then appends " ...". The
"; dir <DIR>; window <ref>" tail comes last, and here <DIR> is about 110
characters (the run root under ~/.local/state/openRepoTools/tmp/...), so
the tail is cut: in repoA after the path, in repoCase inside it, where
trimming to the last space drops the path whole. Both "got" strings are
exactly that cut.
Not T018's: `git diff origin/main...HEAD -- lane-start
tests/test_lane_helpers.sh tests/test_lane_helpers_suite.py` is empty,
and T018's lanes-edit.sh hunks (usage, project_legs/assembly_legs,
lane_reconcile, a comment on lane_worktree_roots, the guard exemption,
the project-legs arm and the refusal list) touch neither set-row-state
nor the stamp. Lane 2 measured the same group failing on main's own tree
at a TMPDIR length of 86 (#186 comment 6244714538: repoA, repoA11b,
repoCase and repoMix); #215 (61a7378) moved only repoA11b to a short
fixed path, and repoA11b passed here. CI's tests job, with a short run
root, passes this test on main (83d2690: 1498 passed, 19 skipped;
f5c3bcd and 2cb14a9: success). Not re-run on main locally: the two cases
run only inside the hour-long shell suite.

This run covers 9f23a6b and bf2a220 (the unreadable-leg reads) and this
round: the S1, S2 and N1-N4 fixes (5d2c1c8), lane 1's rulings in §7
(9a8f175), measurement finding 1 (a4e0b17), the bench-root respelling
(a60cf1c), and the merges of main 2732d59, 83d2690, f595e44 (the
openRepoShape pin at v1.1.0) and f5c3bcd (cf7db4a, 107958c, 69f0017,
88ee9d9). The earlier run on 107958c (t018-verify-20261018T011535Z.log)
was killed at a session exit after one F (first in order:
test_the_lane_helper_suite_passes) and 176 passes, with no summary.

Correction to a60cf1c's message: the first of the seven respelled
lines is in §1 (Scope and method), not §0.

Main has since moved to 2cb14a9 and a41adb6; this head does not
include them.

Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* T018 tests: the unreadable-leg tests ask the git under test what it says, instead of expecting git 2.43's wording (PR #246 CI)

PR #246's Linux tests job (run 39113292535, job 117633782691) failed
two tests: test_t018_reconcile_reports_a_leg_git_cannot_read_as_the_
sweep_counts_it and test_t018_the_handoff_reports_a_leg_git_cannot_
read_as_the_sweep_counts_it. Both wrote a spec leg's .git as
`gitdir: /nonexistent/t018-spec` and expected git's quoted answer to
contain that path. Git words that condition by version:
- 2.43 (this bench): fatal: not a git repository: /nonexistent/t018-spec
- 2.54 (alpine/git:latest, a throwaway container): fatal: not a git
  repository (or any parent up to mount point /)
- 2.55 (CI's runner): fatal: not a git repository: (null)
On CI the tools still classified the leg `unreadable`, counted it and
named it: lane-handoff's stderr shows "the leg .../triad/spec is
unreadable: ... git could not read it: fatal: not a git repository:
(null)". The code reads the condition (git's toplevel there is not the
leg, and a .git stands in it) and only quotes git's line, so the
code is right and the tests were wrong.

tests/test_triad_lane_tooling_handoff.py: a new helper, git_says(b,
leg), returns what every reader quotes (probe_leg's _first_line,
lane-reconcile's and lane-handoff's first non-empty line, or "git
answers <toplevel> there"), asked of the git under test. The three
gitfile checks use it. The empty-.git-directory checks keep "git
answers <assembly> there", the tools' own words, which passed on CI.
No tool changes; S1 and S2 are untouched.

Before this commit: under a PATH shim that rewrites only the
/nonexistent error line to 2.55's "(null)" form, both tests passed
when called directly outside pytest (13 s and 37 s).

Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Re-run CI on main fe58808: refs/pull/246/merge was stale on 14239b9

No content change: this commit's tree is ea4db0d's. Lane openRepoTools-1, the lander, pushed it on lane openRepoTools-3's word in #186 6250521287, so the landing gate's Linux run tests the merge with current main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brett Heap <service@opensoft.dev>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready landing gate: runs tests-macos once before the squash

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant